<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>Document</title>
</head>
<body>
1.ECMAScript、JavaScript、NodeJs,它们的区别是什么?
ECMAScript: 简称es,是一个语言标准(循环、判断、变量、数组等数据类型)
JavaScript: 运行在浏览器端的语言，该语言使用es标准。 es + web api = JavaScript
NodeJs: 运行在服务器端的语言，该语言使用es标准 es + node api = NodeJs
无论是Javascript，还是NodeJs,他们都是es的超集(super set)

为什么es6如此重要?
es6解决了js无法开发大型应用的语言层面的问题

es5的问题:
1.变量可重复声明
2.变量提升：怪异的数据访问
<script>
	if(Math.random()< 0.5){
		var a = "abc";
		console.log(a)
	}else{
		console.log(a)
	}
	console.log(a)
</script>
相当于这样：
<script>
	var a;
	if(Math.random()< 0.5){
		a = "abc";
		console.log(a)
	}else{
		console.log(a)
	}
	console.log(a)
</script>
3.变量提升：闭包问题
<script>
	for( var i = 1; i < 10; i++){
		var btn = document.createElement('button');
		btn.innerHTML = "按钮" + i;
		div.appendChild(btn);
		btn.onclick = function(){
			console.log(i)
		}
	}
</script>
相当于：
<script>
	var i;
	for(i = 1; i < 10; i++){
		var btn = document.createElement('button');
		btn.innerHTML = "按钮" + i;
		div.appendChild(btn);
		btn.onclick = function(){
			console.log(i)
		}
	}
</script>
3.全局变量挂载到全局对象：全局对象成员污染问题
*****************
*              	*
*   let&const	*
*               *
*****************
<!-- 阮一峰ES6:http://es6.ruanyifeng.com/?search=import&x=0&y=0 -->
<script type="text/javascript">
	//块级作用域
	//全局作用域 window
	//函数作用域 由于函数执行产生的作用域被称为函数作用域
	//闭包 在函数外部去返回一个本应该被释放掉的内存
	//let声明的变量不会挂载到全局对象
	// 使用let不会有变量提升，因此，不能在定义let变量之前使用他

	// 底层实现上，let声明的变量实际上也会有提升，但是提升后会将其放入到“暂时性死区”，如果访问的变量位于暂时性死区，则会报错："Cannot access 'a' brfore initialization"。当代码运行到该变量的声明语句时，会将其从暂时性死区中移除。

	// 在循环中，用let声明的循环变量，会特殊处理，每次进入循环体，都会开启一个新的作用域，并且将循环变量绑定到该作用域（每次循环，使用的是一个全新的循环变量）。在循环中使用let声明的循环变量，在循环后会销毁

	// const和let完全相同，仅在于用const声明的变量，必须在声明时赋值，并且不可以重新赋值。
	// 实际上，在开发中，应该尽量使用const来声明变量，以保证变量的值不会随意篡改，原因如下：
	// 1.根据经验，开发中的很多变量，都是不会改变的，也不应该改变的。
	// 2.后续的很多框架或者是第三方JS库，都要求数据不变，使用常量可以一定程度上保证这一点。

	// 注意的细节：
	// 1.常量不可变，是指声明的常量的内存空间不可变，并不保证内存空间中的地址指向的其他空间不可变。
	// 在for(let i = 0; i < len; i++){}中不可以使用const，因为有i++，但在for(const prop in obj){}中可以使用const
	if(false){
		var a = 10;
	}else{
		console.log(a);		//打印undefined,不会报错
	}
	//
	if(false){
		a = 10;
	}else{
		console.log(a);		//会报错,a未定义
	}	
	//块级作用域
	//形式: {}
	//特点: 在块级作用域当中定义的变量,他的作用域只会在当前的作用域下有效	
	{		
		let a = 10;
		var b = 20;
		{
			let c = 30;
			console.log(a);
		}
	//	console.log(c); //会报错,c未定义
	}
	console.log(b);		//打印20
	console.log(a);		//会报错a未定义

    //let无法重复声明
    let p = '1';
    let p = '2'; 		//报错'p' has already been declared

    //let不存在变量提升
    console.log(a);
    let a = 10; 		//报错a is not defined

    //暂时性死区(TDZ temporal dead zone)
    //	js引擎在扫描代码的时候(预编译)
    //	var			变量声明提升
    //	let a = 1 	把a放到TDZ当中
    //  如果使用的变量在TDZ当中就会报错
    //  let a = 1 在遇到声明变量的那段代码的时候会把a从TDZ中拿出
    //直接输入:
    console.log(typeof a); 	//undefined, 不报错
    //
    typeof a;				//报错a is not defined
    let a = 10;

    //
    let x = x; 	//报错x is not defined
    //1-> 	TDZ = [x];
    //2-> 	let x = x;
    //先用右边的x,x未定义报错,再声明左边的x,x从TDZ中释放

    //window下的TDZ = [x]
    let x = 10;
    {	//块级作用域下的TDZ = [x],这个TDZ与windows下的TDZ不是同一个TDZ
    	let x = x;		//报错,x未定义,与上面的报一样的错
    }

    //
    let i = 'window';
    for(let i = 0; i < 10; i++){
    	let i;			//不报错,说明这3个i不在同一个作用域下
    }
    console.log(i);		//打印window

    //for循环逻辑结构:
    let i = 'window';
    {
    	let i = 0;
    	while(i++ < 10)	//--> i只是充当循环次数的作用
    	{
    		console.log('scope2');	//打印10次scope2
    	}
    }
    console.log(i);		//window

    //再谈闭包:
    var arr = [];
    for(var i = 0; i < 10; i++){
    	arr[i] = function(){
    		console.log(i)
    	}
    }
    arr[6]();						//打印10;
    //闭包产生原因:
    //首先，此处的i是全局变量，在for循环结束后，i=10。
    //其次，循环中，数组arr中的所有值都指向function(){console.log(i)}，这个方法被不断的重写，
    //即i=0时，指向function(){console.log(i)}；i=1时，i=0和i=1都指向了同一个function(){console.log(i)}；
    //执行到a[9]时,arr[0]~arr[9]都指向了function(){console.log(i)}(并非为同一个function, 打印arr[1]==arr为false)
    //但是i又每次++,所以i变成了10。

    //4种解决闭包的方法:
    //方法1
    //使用let解决闭包问题,原理是let声明的变量不会暴露给全局:
    var arr = [];
    for(let i = 0; i < 10; i++){
    	arr[i] = function(){
    		console.log(i)
    	}
    }
    arr[6]();						//打印6;
    //方法2
    //常规解决闭包方案:
    var arr = [];
    for(var i = 0; i < 10; i++){
    	arr[i] = (function(j){
    		return function(){
    			console.log(j);
    		}
    	})(i)
    }
    arr[6]();						//打印6;
    //方法3,转化为ES5语法:
    var arr = [];
    var _loop = function _loop(i){
    	arr[i] = function(){
    		console.log(i);
    	};
    };
    for(var i = 0; i < 10; i++){
    	_loop(i);
    }
    arr[6]();						//打印6;
    //方法4,中间嵌套一层let
    var arr = [];
    for(var i = 0; i < 10; i++){
    	let j = i;
    	arr[j] = function(){
    		console.log(j);
    	};
    }
    arr[6]();						//打印6;
    //但是如果这样就又打印10了:
    var arr = [];
    for(var i = 0; i < 10; i++){
    	arr[i] = function(){
    		let j = i;
    		console.log(j);
    	};
    }
    arr[6]();						//打印6;

    //所以这个for循环展开后相当于
    //i = 0时:
    {
    	let j = i;
    	arr[i] = function(){
    		console.log(j);
    	};
    }
    //i = 1时:
    {
    	let j = i;
    	arr[i] = function(){
    		console.log(j);
    	};
    }
    //i = 2时:
    {
    	let j = i;
    	arr[i] = function(){
    		console.log(j);
    	};
    }
    //...
    //i等于每个值时都有一个自己的块级作用域

</script>
*****************
*              	*
*     const		*
*               *
*****************
<script type="text/javascript">
	//const声明的变量地址不可以更改,表现在原始值上就是声明常量,表现在引用值上就是地址不可变,但里面的值可以改变
	//const也存在TDZ也不可以重复声明

    const div = {};
    div.a = 10;

</script>
*****************
*              	*
*  	函数部分	 *
*               *
*****************
<script type="text/javascript">
	//返回长度为n的随机数字符串的函数
	function Random(n){
		let max = 1;
		for(let i = 0; i <　n; i++){
			max *= 10;
		}
		return function (){
			let str = '' + parseInt(Math.random() * max);
			let count = n - str.length;			
			//补0操作,Math.random()产生的随机数有可能小数点后的前几位是0,比如0.01,*max之后可能会少位,所以要补位
			for(let i = 0; i < count; i++){
				str += '0';
			}
			return str;
		}
	}
	let random8 = Random(8);
	console.log(random8());
</script>
*****************
*              	*
*   函数默认值	*
*               *
*****************
<script type="text/javascript">
	//传统方式
	function Person(name){
		this.name = name === undefined ? 'none' : name;
	}
	let p = new Person('wxb');
	let p1 = new Person();
	let p1 = new Person(null);
 	let p1 = new Person(undefined);
	let p1 = new Person(0);
	let p1 = new Person('');
	let p1 = new Person(false);
	//ES6写法
	function Person(name = 'none'){
		this.name = name;	
	}
	//默认值只有在参数值绝对等于undefined(===undefined)时才会起作用
	function Person(name, age = 0, weight){
		this.name = name;
		this.age = age;
		this.weight = weight;
	}
	let p1 = new Person('wxb', undefined, 30);
	//控制台输入p1 打印 Person {name: "wxb", age: 0, weight: 30}

    //arguments对默认参数的影响
    //传统模式
    function max(num1, num2){
    	console.log(num1, arguments[0]);
    	console.log(num2, arguments[1]);
    	num1 = 4;
    	console.log(num1, arguments[0]);
    	arguments[0] = 6;
    	console.log(num1, arguments[0]);
    }
    max(1,2);				//打印1 1 // 2 2 // 4 4 // 6 6
    //严格模式
    function max(num1, num2){
    	'use strict'
    	console.log(num1, arguments[0]);
    	console.log(num2, arguments[1]);
    	num1 = 4;
    	console.log(num1, arguments[0]);
    	arguments[0] = 6;
    	console.log(num1, arguments[0]);
    }
    max(1,2);				//打印1 1 // 2 2 // 4 1 // 4 6

    //使用默认参数的话函数体内部自动变为严格模式:
    function max(num1 = 0, num2){
    	console.log(num1, arguments[0]);
    	console.log(num2, arguments[1]);
    	num1 = 4;
    	console.log(num1, arguments[0]);
    	arguments[0] = 6;
    	console.log(num1, arguments[0]);
    }
    max(1,2);				//打印1 1 // 2 2 // 4 1 // 4 6

    //惰性求值,如果不用到默认值就不会去取默认值:
    function getValue(){
    	console.log('hello');
    	return 6;
    }
    function count(n,m = getValue()){
    	console.log(n + m);
    }
    count(1,1)		//打印2; 用不到默认参数,所以getValue函数不执行
    count(1)		//打印7;

    //TDZ
    function add(m, n = m){
    	console.log(m + n);
    }
    add(1,2);		//打印3;
    add(1);			//打印2;
    //	先对代码扫描	TDZ = [m, n];
    //	let m;		TDZ = [n];
    //	let n = m; 	TDZ = [];

    function add(m = n, n){
    	console.log(m + n);
    }
    add(undefined, 2);		//报错
    //	先对代码扫描	TDZ = [m, n];
    //	let m = n;	TDZ = [m, n]; -->报错
    //	let n; 		TDZ = [];

</script>
*****************************
*							*
*		解构赋值...arg 		*
*	无命名参数和扩展运算符	*
*							*
*****************************
<script type="text/javascript">
	//参数不定的时候:通过arguments[]访问不直接,不方便
	//箭头函数不支持arguments
	//...arg(不定参数):
	//把若干数字+1,添加到指定数组当中
	//每个函数中有且最多只能有一个不定参数,不可有默认值,且必须是最后一个参数
	function count(arr, ...arg){
		console.log(arg);						//__proto__: Array(0)	说明是数组
		console.log(arguments);					//__proto__:Object		说明是对象
		for(let i = 0; i < arg.length; i++){
			arr[i] = arg[i] + 1;
		}
	}
	let arr = [];
	count(arr,1,2,3,4);							
	//打印 [1, 2, 3, 4]
	//打印 [Array[0], 1, 2, 3, 4]
	//输入arr,打印(4) [2, 3, 4, 5]

    //扩展运算符
    //常规求数组中最大值:
    let arr = [1,4,7,8,5,2];
    function max(){

    }
    max.apply(window, arr);			//需要调用window显得很难受

    //...扒掉最外面括号操作符([],{})
    //...[1,2,3,[5]] = 1,2,3,[5]
    //使用...：
    max(...arr);
    //相当于下面
    max(1,4,7,8,5,2);

    //常规合并两个数组:
    var arr1 = [1,2,3,4];
    var arr2 = [5,6,7,8];
    console.log(arr1.concat(arr2));	//(8) [1, 2, 3, 4, 5, 6, 7, 8]
    console.log(arr1);				//(4) [1, 2, 3, 4]
    console.log(arr2);				//(4) [5, 6, 7, 8]

    //使用...：
    var arr3 = [...arr1, ...arr2];
    console.log(arr3)				//(8) [1, 2, 3, 4, 5, 6, 7, 8]

</script>
*****************************
*							*
*		箭头函数() => {} 	 *
*							*
*****************************
<script type="text/javascript">
	//箭头函数 =>
	//基本结构
		() => {}
	//传统函数结构
		function(){}
	//对于小括号(),函数的参数有且只有一个时才可以不写;
	//对于{},函数体仅仅有一条语句的时候可以不写:
	let getname = name => name;
	//相当于:
	let getname = function(name){
		return name;
	}
	//带有小括号和花括号的形式:
	let show = (name, age) =>{
		console.log(name, age);
		console.log(name);
	}
	//不写参数的形式:
	let fn = () => {};
	//相当于:
	function fn(){};
	
	//想返回一个对象:
	let returnObj = (name) => ({name:name});
	//控制台输入returnObj('lyc')	会打印{name: "lyc"}
	//相当于	let returnObj = (name) => {return {name:name}};
	//需要在{name:name}外面加一层()，是因为不加的话系统会认为是一层花括号

    //立即执行箭头函数
    let fn = (name => name)('lyc');
    //相当于
    let name = function(name){return name}('lyc');
    //控制台输入fn 	会打印'lyc'

    //箭头函数中没有this,没有arguments,没有super(class),没有prototype
    //不能被作为构造函数,不能被new操作符执行,更多情况下用于计算,代表的是数据流向,方便javascript引擎优化代码:
    let fn = () => {}
    new fn();		//报错

    //箭头函数具有绑定this的能力
    //this绑定分为四种,权重从上到下依次降低:

    // new绑定;
    // 显示绑定(apply,call,bind);
    // 隐式绑定(谁调用,this指向谁);
    // 默认绑定(空执行函数(函数单纯执行),this指向window);

    //再谈this指向:
    var name = 'window';
    var obj = {
    	name: 'obj',
    	print: function(){
    		console.log(this.name);
    	}
    }
    obj.print();							//obj;
    var newPrint = obj.print.bind(window);	//此时newPrint变为f(){console.log(this.name);}
    var newnewPrint = newPrint.bind(obj);	//此时newnewPrint变为ƒ () { [native code] }
    newPrint();								//window;
    newnewPrint();							//window;

    //箭头函数中没有this,箭头函数具有绑定this的能力,
    //箭头函数中绑定的this是离自己最近的父级非箭头函数作用域中的this
    var name = 'window';
    var obj1 = {
    	name: 'obj1',
    	print: () => console.log(this.name)
    }
    obj1.print();							//window;
    var newPrint = obj1.print.bind(obj1);
    newPrint();								//window;

    //
    let obj3 = {
    	name: 'obj3',
    	getName: function(){
    		let show = () => console.log(this.name);
    		show();
    	}
    }
    obj3.getName();							//打印obj3
    //分析:
    //scope2(show),show函数是箭头函数，没有this,所以要向上找,向上是scope1(getName)
    //scope1(getName),scope1(getName)是在obj3下的函数,所以在scope1(getName)里this指向obj3
    //所以最终在scope2(show)打印的this指向obj3

    //
    var name = 'window';
    function scope(){
    	setTimeout(() => {
    		console.log(this.name);
    	}, 10);
    }
    scope.call({name: 'now you see me'});	//隔10ms后会打印'now you see me'

    var name = 'window';
    function scope(){
    	setTimeout(function(){
    		console.log(this.name);
    	}, 10);
    }
    scope.call({name: 'now you see me'});	//隔10ms后会打印'window'

</script>
*****************
*              	*
*   对象扩展	*
*               *
*****************
<script type="text/javascript">
	//生成对象方法:
	//对象字面量
	let obj = {};
	//new
	let obj1 = new Object();
	//Object.create(指定原型创建)
	let obj2 = Object.create(null);		//纯野生对象

    //对象的增加和修改:
    obj.name = 'xxx';
    obj['name'] = 'xxx';
    //对象内容的删除:
    delete obj.name;
    //对象内容的查找:
    obj.name;

    //ES6扩展
    //对象内属性和方法简写:
    let person = {
    	name,
    	sayName(){
    		console.log(this.name);
    	}
    }
    //可计算属性
    let front = 'elem-',
    	name = 'zhangsan',
    	stuff = 'stuff';
    let stuffList = {
    	[front + stuff]: name
    }
    console.log(stuffList);					//打印{elem-stuff: "zhangsan"};
    console.log(stuffList['elem-' + stuff]);//打印"zhangsan";

---

    //ES6中关于对象新增的一些方法
    //ECMA定义新的方法不会在全局、Object.prototype下定义
    //所以导致出现静态方法
    Object.is() 加强的比较运算符:

    console.log(NaN == NaN);				//false;
    console.log(NaN === NaN);				//false;
    console.log(Object.is(NaN,NaN));		//true;

    console.log(Object.is(+0,-0));			//fasle;
    console.log(+0 === -0);					//true;
    console.log(Object.is([],[]));			//false;引用值比较的是地址

---

    Object.assign() 属性叠加混合:
    //把friend对象上的属性copy到person对象上
    let person = {
    	name: 'aaa',
    	age: 18,
    	height: 180,
    }
    let friend = {
    	weight: 100,
    	height:100
    }
    let obj ={
    	name: 'bbb'
    }
    let p = Object.assign(person, friend, obj);
    console.log(person);		//{name: "bbb", age: 18, height: 100, weight: 100}
    console.log(p == person);	//true,assign返回值不改变地址

---

    Object.keys / Object.values / Object.entries:

    let person = {
    	name: 'aaa',
    	age: 18,
    	height: 100
    }
    console.log(Object.keys(person));		//(3) ["name", "age", "height"]
    console.log(Object.values(person));		//(3) ["aaa", 18, 100]
    console.log(Object.entries(person));	//0:(2) ["name", "aaa"],1:(2) ["age", 18],2:(2) ["height", 100]

---

    //增强对象原型
    let p = {
    	sayName(){
    		console.log(this.name);
    	}
    }
    let person = Object.create(p);
    console.log(person);					//{}
    console.log(person.__proto__);			//{sayName: ƒ}
    console.log(person.__proto__ == p);		//true
    //ES5中暴露的接口,正常访问原型的方法
    console.log(Object.getPrototypeOf(person));			//{sayName: ƒ}
    console.log(Object.getPrototypeOf(person) == p);	//true

    //
    function Person(name = 'none'){
    	this.name = name;
    }
    Person.prototype.sayName = function(){
    	console.log(this.name);
    }
    var p1 = new Person();
    p1.sayName();							//none;

    //
    function Person(name = 'none'){
    	this.name = name;
    }
    Person.prototype.sayName = function(){
    	console.log(this.name + 'proto');
    }
    var p2 = new Person();
    p2.sayName();							//noneproto;

---

    //ES6新增方法:
    Object.setPrototypeOf(p1, p2):		//让p1的原型等于p2

    function Person(name = 'none'){
    	this.name = name;
    }

    var p1 = new Person();
    let obj = {
    	sayName(){
    		console.log(this.name + 'obj');
    	}
    }
    Object.setPrototypeOf(p1, obj);

    p1.sayName();							//noneobj

---

    super:

    function Person(name = 'none'){
    	this.name = name;
    }
    Person.prototype.sayName = function(){
    	console.log(this.name);
    }

    var p1 = new Person();
    let p3 = {
    	name: 'p3',
    	sayName(){					//函数的简写形式
    		//super相当于Object.getPrototypeOf(this);
    		return Object.getPrototypeOf(this).sayName.call(this);
    		//相当于
    		return super.sayName.call(this);
    	}
    }
    Object.setPrototypeOf(p3, p1);	//由于p3是通过字面量方法创建的对象,无法添加原型,所以如果不写这句话下面的语句会报错,报错原因是p3原型上没有sayName函数
    p3.sayName();					//打印 p3;

    //super只在简写函数里才有用,原因是任何函数里(包括简写函数与非简写函数)都有一个私有属性值[[homeObject]],
    //只有在简写函数sayName(){...}中,才会使homeObject默认绑定为p3,如果不使用简写形式书写函数sayName则会报错:
    let p3 = {
    	name: 'p3',
    	sayName: function(){			//函数的正常书写形式（非简写形式）
    		super.sayName.call(this);	//报错'super' keyword unexpected here
    	}
    }

---

    //解构赋值,实质是模式匹配

    let resData = {
    	director: 'xxx',
    	imgs: [],
    	casts: {}
    }
    let {director, imgs, casts} = resData;
    console.log(director); 	//打印xxx
    console.log(imgs);		//打印[]
    console.log(casts);		//打印{}
    //原理:
    //相当于:
    let {director: director, imgs: imgs, casts: casts} = resData = {director: 'xxx', imgs: [], casts: {}};

    //代码块无法被赋值,{}就是一个代码块,所以以下写法会报错:
    let director, imgs, casts;
    {director, imgs, casts} = resData;		//{director, imgs, casts}是一个代码块无法被赋值,会报错
    //但可以外面加一个(),然后根据执行顺序会先执行()里的操作,也就是解构赋值操作:
    let director, imgs, casts;
    ({director, imgs, casts} = resData);

    //解构失败,解构出来的直为 undefined:
    let {director, imgs, casts, value} = resData;
    console.log(value);		//undefined;对于value来说就是解构失败
    //不完全解构
    let {director, imgs} = resData;
    let {PI, random, pow, floor} = Math;
    //解构赋值可以传默认值:
    let {PI = 3.14, random, pow, floor, a = 0} = Math;
    console.log(PI);		//打印3.141592653589793	Math里有PI会覆盖默认值
    console.log(a);			//打印0 	Math里没有a会使用默认值

    //嵌套解构
    let node = {
    	type: 1,
    	parentNode: {
    		tagName: 'div',
    		values: {
    			value: 123
    		}
    	}
    }
    let {parentNode, parentNode: {tagName, values: {value}}} = node;
    console.log(parentNode)	//打印{tagName: "div", values: {…}}
    console.log(tagName)	//打印'div'
    console.log(value)		//打印123

    //通过解构赋值取出下面对象里的subjects,rating,average,'剧情','喜剧'：
    let movieList = {
    	start: 0,
    	count: 1,
    	subjects: [
    		{
    			rating: {
    				max: 10,
    				min: 0,
    				average: 9.6,
    			},
    			genres: ['剧情', '喜剧'],
    			casts: 1,
    			title: '我不是药神',
    		}
    	]
    }
    let {subjects, subjects:[{rating, rating: {average}, genres:[feature, comic]}]} = movieList

    //数组的解构赋值
    let [a, b, c] = [1, 2, 3]
    let [d, [e], f]	= [1, [2], 3]
    let [g = 10, h, i] = [, , 10]
    console.log(a);					//1
    console.log(b);					//2
    console.log(c);					//3
    console.log(d);					//1
    console.log(e);					//2
    console.log(f);					//3
    console.log(g);					//10
    console.log(h);					//undefined
    console.log(i);					//10

    //互换a, b的值:
    let a = 1, b = 2;
    [a, b] = [b, a];
    console.log(a);		//2
    console.log(b);		//1
    //原理:
    //0x000 存a = 1
    //0x001 存b = 2
    //0x002 存a = 0x001 存b = 2
    //0x003 存b = 0x000 存a = 1

</script>
*****************
*              	*
*    数据结构	*
*               *
*****************
<script type="text/javascript">
	//数据结构 计算机中组织数据的方式

    let arr = [1, 2, 3];	//连续地址
    //数组特点:有序，支持栈，队列操作，不定长
    //栈操作: 	入栈arr.push,出栈arr.pop
    //队列操作:  入队arr.push,出队arr.shift

    //ES6新的数据结构
    map.set、 map.get、 map.has、 map.delete、 map.clear、 map.forEach:
    //Map是字典,加强的对象,Map是若干键值对的集合,Map能识别"1"与1是不同的键,obj却识别不了:

    let map = new Map();
    	map.set('1', '2');
    	map.set(1, '2');

    let obj = {};
    	obj['1'] = '2';
    	obj[1] = '2';
    console.log(map, obj);		//Map(2) {"1" => "2", 1 => "2"} {1: "2"}
    console.log(map.get('1'));	//2
    console.log(obj['1']);		//2

    console.log(map.has(1))		//true
    console.log(map.has('1'))	//true
    console.log(map.has(2))		//false

    map.delete(1);
    console.log(map.has(1));	//false
    map.clear();
    console.log(map);			//Map(0) {}

    map.forEach((value, key, map) => console.log(value, key, map))		//遍历每个键值对
    //2 1 Map(2) {"1" => "2", 1 => "2"}
    //2 1 Map(2) {"1" => "2", 1 => "2"}

    let map = new Map([['name', 'lyc'], ['age', 18], [1, 2]]);			//map初始化,必须使用二维数组
    console.log(map);													//Map(3) {"name" => "lyc", "age" => 18, 1 => 2}

    //ES5数组去重:
    let arr = [1,1,2,3,2,1,5,2,5,3,2];
    Array.prototype.unique = function(){
    	let itemIndex = {};
    	let resArr = [];
    	this.forEach(item => {
    		if(!itemIndex[item]){
    			itemIndex[item] = 1;
    			resArr.push(item);
    		}
    	})
    	return resArr;
    }
    console.log(arr.unique());		//(4) [1, 2, 3, 5]
    //这种方法所遇到的问题:
    arr = [1, '1', 1]				//去重后打印[1], '1'并没有被保留下来
    arr = [1,{value: 1},{value: 2}]	//去重后打印[1, {value:1}], {value:2}并没有被保留下来,因为obj在itemIndex[item]变成了[object Object]

    //基于ES6的数组去重:
    let obj = {},
    arr = [1, '1', 1, obj, 1, obj];
    Array.prototype.unique = function(){
    	let itemIndex = new Map();
    	let resArr = [];
    	this.forEach(item => {
    		if(!itemIndex.has(item)){
    			itemIndex.set(item, 1);
    			resArr.push(item);
    		}
    	})
    	return resArr;
    }
    console.log(arr.unique())		//(3) [1, "1", {…}]

</script>
*************************
*              			*
*    哈希算法&邻接列表	*
*               		*
*************************
<script type="text/javascript">
	//手动使用hash算法封装Map方法:
	myMap.prototype.len = 8						//定义桶个数为8
	myMap.prototype.bucket = []					//初始化定义所有的桶合在一起为一个大数组(其实这个数组是一个二维数组,每个分桶分别为bucket[0]、bucket[1]....)

    myMap.prototype.init = function(){			//初始化函数
    	for(let i = 0; i < this.len; i++){
    		this.bucket[i] = {next: null}		//初始化每个桶,让每个桶的下一位都为null(现在每个桶的的第一个节点都为{next: null})
    	}
    }
    myMap.prototype.makeHash = function(key){	//通过哈希算法把key合理的转化为数字
    	let hash = 0;
    	if((typeof key) == 'string'){			//用来处理字符串转为哈希值
    		//取后三位字符串处理
    		let len = (key.length > 3) ? key.length : 3
    		for(let i = len - 3; i < len; i++){	//从倒数第3位开始循环,一直循环到最后一位
    			hash += (key[i] !== undefined) ? key[i].charCodeAt() : 0
    		}
    	}else{
    		hash = +key							//用来转化非字符串的数为数字类型
    	}
    	return hash
    }
    myMap.prototype.set = function(key, value){
    	//常规方法使用forEach循环,这样查找效率低
    	// this.__entries__.forEach(item => {
    	// 		if(item.key == key){
    	// 			item.value = value
    	// 		}else{
    	// 			arr.push([key, value])
    	// 		}
    	// })

    	//哈希算法:
    	let hash = this.makeHash(key);					//把传进来的key值进行哈希操作
    	let list = this.bucket[hash % this.len]			//确定在哪个桶里(bucket[0]/bucket[1]/...)
    													//第一次执行时list = this.bucket[i] = {next: null}
    	let nextNode = list 							//每次执行set函数时都让nextNode指向桶的序号(key值所对应的桶的头部)
    													//第一次执行时nextNode = list = this.bucket[i] = {next: null}
    	while(nextNode.next){							//如果nextNode.next值存在(证明这个桶还没找到头)
    		if(nextNode.key === key){					//这里的代码是处理如果在这条桶链上发现了与key同名的键值对的节点,则直接修改这个键值对就行了
    			nextNode.value = value
    			return 									//找到了直接return,因为是set操作,如果找到了就直接跳出就行
    		}else{										//这里的代码是处理在这条桶链上,只要没找到与key同名的键值对节点,就一直向后找(找下一个节点)
    			nextNode = nextNode.next
    		}
    	}
    	nextNode.next = {key, value, next: null};		//如果能执行到这步说明在整条桶链上都没找到同名键值对的节点。
    													//能执行到这步只有两种情况:一是next值为null(第一次传入key时nextNode.next = null)
    													//二是nextNode里已经有值但找到头都没找到,说明这个桶里没有key所对应的键值对
    													//则给nextNode添加这个键值对节点,因为nextNode、list、this.bucket[i]都为引用值所以一起跟着变
    													//第1次添加键值对后:nextNode = list = this.bucket[i] = {key, value, next: null}
    													//第2次:nextNode = list = this.bucket[i] = {key, value, next: {key, value, next: null}}...
    }
    myMap.prototype.get = function(key){
    	let hash = this.makeHash(key);					//把传进来的值进行哈希操作
    	let list = this.bucket[hash % this.len]			//确定在哪个桶里
    	let nextNode = list
    	while(nextNode.next){							//如果有nextNode.next值
    		if(nextNode.key === key){
    			return nextNode.value					//找到了直接return next.value
    		}else{
    			nextNode = nextNode.next 				//如果nextNode.key ！=== key,就让nextNode = nextNode.next,
    													//下次再进入while时nextNode.next其实就是nextNode.next.next了
    		}
    	}
    	return 											//如果没有next值,(找到头都没找到)跳出循环
    }
    myMap.prototype.has = function(key){
    	let hash = this.makeHash(key);					//把传进来的值进行哈希操作
    	let list = this.bucket[hash % this.len]			//确定在哪个桶里
    	let nextNode = list
    	while(nextNode.next){							//如果有nextNode.next值
    		if(nextNode.key === key){
    			return true								//找到了直接返回true
    		}else{
    			nextNode = nextNode.next
    		}
    	}
    	return false 									//没找到就返回false
    }
    myMap.prototype.delete = function(key){
    	let hash = this.makeHash(key);					//把传进来的值进行哈希操作
    	let list = this.bucket[hash % this.len]			//确定在哪个桶里
    	let nextNode = list
    	while(nextNode.next){							//如果有nextNode.next值
    		if(nextNode.next.key === key){
    			nextNode.next = nextNode.next.next 		//如果这个节点的下一个节点的key值与想找的key值相同,就让这个节点的next指向nextNode.next.next
    			return true								//找到了直接返回true
    		}else{
    			nextNode = nextNode.next
    		}
    	}
    	return false
    }
    myMap.prototype.clear = function(){
    	this.init();
    }

    function myMap(){
    	this.init();
    	//this.__entries__ = [[key: value], [key: value], [key: value]]
    }
    let m = new myMap();

    //控制台输入
    m.set(1, 1);
    m.set('1', 1);
    m.set(9, 1);
    m.has(1);
    m.has(2);
    m.get('1');
    m.delete(1);
    m.clear();

---

    WeakMap 弱引用,key值只能使用对象:
    let Wmap = new WeakMap()

    //内存回收机制,两种情况:
    //【1】定时清理内存
    //【2】内存达到一定量时清理

---

Set:
//特点: 有序列表,包含相互独立且互不相等的值
let set = new Set([1,2,3,4,4,4,4,1,'1']);
console.log(set); //set{1,2,3,4,'1'}
//添加值
set.add(1);
console.log(set); //set{1,2,3,4,'1'}
let obj = {};
set.add(obj);
console.log(set); //set{1, 2, 3, 4, "1", …}
//删除
set.delete(obj);
//查找
set.has(1);
//forEach
set.forEach((item, i, set) => console.log(item, i, set)) //1 1 Set(5) {1, 2, 3, 4, "1"}
//2 2 Set(5) {1, 2, 3, 4, "1"}
//3 3 Set(5) {1, 2, 3, 4, "1"}
//4 4 Set(5) {1, 2, 3, 4, "1"}
//1 1 Set(5) {1, 2, 3, 4, "1"}
//扩展运算符
var arr = [...set]
console.log(arr); //(5) [1, 2, 3, 4, "1"]
//set 比较的是地址引用值对应的地址不相同的话也可以被当成不一样的值：
set.add({});
set.add({});
//此时 set 里有两个{},因为地址不同;

    //弱引用
    var ws = new WeakSet();
    //只能传引用值

</script>
*********************
*              		*
*    字符串&数组 	*
*               	*
*********************
*	   字符串		*
*********************
<script type="text/javascript">
	//es5字符串:
	//String.prototype.charAt(num)
	//charCodeAt、slice[start=0, end= this.length)左闭右开
	let str = 'welcome to duyi';
	console.log(str.charAt(3) == str[3]);		//true
	console.log(str.charCodeAt(0));				//99,w的unicode编码是99

    //indexOf(str, start = 0)成功返回起始索引,失败返回-1
    console.log(str.indexOf('come'));			//3
    console.log(str.indexOf('come', 4));		//-1
    //split按照某种方法拆分
    let str = '1,2,3,4,5,6'
    console.log(str.split(','))					//(6) ["1", "2", "3", "4", "5", "6"]
    console.log([1,2,3,4,5,6].join(','))		//1,2,3,4,5,6
    //toUpperCase、toLowerCase,对汉字无影响

---

    //ES6
    includes:
    //相当于:
    // String.prototype.isInclude = function(str){
    // 	if(this.indexOf(str) !== -1){
    // 		return true
    // 	}else{
    // 		return false
    // 	}
    // }
    let str = 'you were, are and will be in my heart';
    console.log(str.includes('heart', 30))						//true, 30是开始的位置
    //
    startsWith endsWith:
    console.log(str.startsWith('you'), str.endsWith('heart'));	//true true
    console.log(str.startsWith('w',4), str.endsWith('you w', 5));	//true true, 从第4位(包含)开始以'w'开始的,截止到第5位(不包含),以'you w'结束的

    //做筛选用:
    let stuffLists = [
    	'Google-Page',
    	'Alibaba-Mayun',
    	'Tencent-Pony',
    	'Facebook-Zuckerberg',
    	'Duyi-Ding',
    	'Duyi-Song',
    	'Duyi-Cheng'
    ]
    let DuyiStuffs = stuffLists.filter(stuff => stuff.startsWith('Duyi-'));
    console.log(DuyiStuffs);				// ["Duyi-Ding", "Duyi-Song", "Duyi-Cheng"]

    repeat:
    //产生由10个*组成的字符串:
    let str = '*'.repeat(10);

</script>

---

- 数组 \*

---

<script type="text/javascript">
	//ES6数组
	//初始化数组
	//字面量 var a = [];
	//构造函数
	var arr1 = new Array(9);		//[empty × 9]
	var arr2 = new Array('7');		//["7"]
	var arr3 = new Array('1',2,3);	//["1", 2, 3]

	//Array.of 加强的Array构造函数,统一返回由参数构成的数组
	var arr1 = Array.of(9);			//[9]
	var arr2 = Array.of('7');		//["7"]
	var arr3 = Array.of('1',2,3);	//(3) ["1", 2, 3]

	//Array.from 将类数组转化为数组
	//类数组:arguments、NodeList
	
	//ES5中让类数组调用数组中的方法:
	Array.prototype.filter.call(likeArr);	//调用数组的filter方法
	//ES6的方法:
	//方法1
	var realArr = Array.from(likeArr);
	//方法2
	let arr = [...likeArr];

	//ES5中indexof:
	var arr = [1,2,3,4];
	console.log(arr.indexOf(4));					//3
	var arr = [1,2,3,4,NaN];
	console.log(arr.indexOf(NaN));					//-1

	//ES6:find(fn)返回具体找到的值
	// 	  findIndex(fn)返回第一个具体找到的值的索引,
	//可以查找NaN和按条件查找:
	console.log(arr.find(x => Object.is(x, NaN)));		//NaN, Object.is(x, NaN)的意思是x是不是等于NaN,是的话返回true,找到了,具体值是NaN
	console.log(arr.findIndex(x => Object.is(x, NaN)));	//4, NaN的索引是4	
	console.log(arr.findIndex(x => x > 2));				//2
*********************************************
	fill(value, start = 0, end = length): 

	var arr = [1, 2, 3];
	arr.fill('*');			//['*', '*', '*']
	arr.fill('$', 1); 		//['*', '$', '$']
	arr.fill('&', 1, 2); 	//['*', '&', '$']	

	//通常用来初始化数组:
	//初始化二维雷盘:
	let arr = new Array(10);
	arr.fill(0);
	let list = new Array(10);
	list.fill(arr);
</script>

---

-                   *
-     	反撇号(`)	  *
- 模板字符串&表达式函数 \*
-                     *

---

<script type="text/javascript">
	let name1 = 'I';
	let name2 = 'you';
	let str = `${name1} love ${name2}`;
	//相当于 let str2 = name1 + ' love ' + name2

	//字符串的回车换行问题
	//ES5下:
	let str = '<div> \n\
					<span></span> \n\
			   </div>'
	console.log(str)
	//ES6下:
	let title = 'hello'
	let newstr = `
				<div>
					<span>${title}</span>
				</div>
				`
	console.log(newstr)	
	//${} 里面可以放原始值和引用值
	//使用引用值会自动调用toString方法
	//{} => [object Object]
	//[1, 2, 3] => 1,2,3
	//表达式 函数
	function returnStr(a, b){
		return `${a} x ${b} = ${a*b}`
	}
	console.log(returnStr(3, 2));		//3 x 2 = 6
	//模板字符串的优点:
	//更标准的字符串,更好的处理了字符串拼接问题
	//语义化更好
	//防止注入xss
	//标签模板 标签=>函数
	
	function print(arg1, arg2, arg3){
		console.log(arguments);
		console.log(arg1, arg2, arg3);
	}
	print`12${3}4${5}6`;				//[Array[3], 3, 5]
										//["12", "4", "6", raw: Array[3]] 3 5
	//按照模板分开,["12", "4", "6"]
	//有几个模板,就把这几个模板中的值做成参数
	
	function print(listArr, ...arg){
		console.log(listArr);
		console.log(arg);		
	}
	var a = 10;
	var b = 9;
	print`${a} x ${b} = ${a*b}`; 		//["", " x ", " = ", "", raw: Array(4)]
										//[10, 9, 90]

	console.log(parseInt`12${3}`)		//5
	//相当于执行
	console.log(parseInt(['12', ''], 3))//5	
	//相当于执行:3进制的12是5
	console.log(parseInt(12, 3))		//5	

	//以下两种语句都可以执行
	alert('hello');
	alert`hi`;

	//防止注入xss
	let name = '张三';
	document.write(`<p>你好,${name}</p>`);	//你好,张三
	//xss注入:
	let name = '<script>console.log("你中招了")<\/script>'
	document.write(`<p>你好,${name}</p>`);	//执行<script>里的代码
	//如何防止xss注入:
	let name = '<script>console.log("你中招了")<\/script>'
	function safeHTML(arr, name){
		return `${arr[0]}${name.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')}${arr[1]}`
	}
	document.write(safeHTML`<p>你好,${name}</p>`);
	//在函数safeHTML里${arr[0]}与${arr[1]}分别对应'<p>你好,'和'</p>';${name}对应的是'<script>console.log("你中招了")<\/script>'
	//然后把${name}里的&、<、>使用'&amp;'、'&lt;''&gt;'替换;这样嵌在标签里面的<scrip>标签就不能执行了
</script>

---

-              		*
- 迭代器&生成器 \*
-               	*

---

- 迭代器 \*

---

<script type="text/javascript">
	//迭代器(遍历器)Iterator:数据的迭代
	//可迭代数据类型:数组(使用for方法)、对象(使用forEach方法)、Map(使用forEach方法)、Set(使用forEach方法)
	//对于数组的迭代:for map forEach filter
	//forEach是加强版本的for,语义化好不用管理参数:
	//遍历一维数组:
	//for
	var arr = [1, 2, 3, 4];
	var len = arr.length;
	for(let i = 0; i < len; i++){
		console.log(arr[i]);
	}
	//forEach
	arr.foeEach(item => console.log(item))
	//遍历二维数组:
	//for
	var arr = [[1, 2], [3, 4, 5]];
	var len = arr.length;
	for(let i = 0; i < len; i++){
		var jlen = arr[i].length;
		for(let j = 0; j < jlen; j++){
			console.log(arr[i][j]);
		}
	}
	//forEach
	arr.forEach(item => {
		item.forEach(i => console.log(i));
	})

	//迭代器是一个对象,专门为可迭代对象设计了统一的接口,这个方法用来遍历数据
	//特点:
	//[1]每个迭代器都有next方法,每次执行next都返回一个结果对象{value, done}
	//[2]value是每次迭代的数据,done表示迭代是否结束,是Boolean值
	//[3]每个迭代器都有专用指针,开始时指向数据结构的第一个值,每次调用next方法, 返回当前值,指针下移
	//[4]每个迭代器都支持(for of)循环
	//举例(结构类似于下面这样,但这只是一个大概结构,还没办法执行):
	let arr = [1, 2, 3]
	Array.prototype.iterator = {
		next(){
			return {
				value,
				done
			}
		}
	}
	arr.iterator.next()    //{value: 1, done: false}
	arr.iterator.next()    //{value: 2, done: false}
	arr.iterator.next()    //{value: 3, done: false}
	arr.iterator.next()    //{value: undefined, done: true}		

	//自己手动创建迭代器对象：
	let arr = [1, 2, 3]
	function createIterator(arr = []){
		let nextIndex = 0		//定义专用指针,初始化。这里使用了闭包,每次调用next时都会把nextIndex重新赋值为nextIndex ++(也就是指针下移)以便于链式调用
		return{
			next(){
				if(nextIndex > arr.length - 1){
					return {
						value: undefined,
						done: true
					}
				}else{
					return {
						value: arr[nextIndex ++],
						done: false
					}
				}
			}
		}
	}
	var i = createIterator(arr);

	console.log(i.next())   	//{value: 1, done: false}
	console.log(i.next())    	//{value: 2, done: false}
	console.log(i.next())    	//{value: 3, done: false}
	console.log(i.next())    	//{value: undefined, done: true}			

	//默认自带Iterator接口的数据结构:
	// Array、Map、Set、String、arguments、NodeLists

	//Array、Map、Set(forEach方法)
	//entries()、keys()、values() 
	let arr = [1, 2, 3, 4]
	let map = new Map([['name', 'web'], ['age', 18], ['hobbit', 'body']])
	let set = new Set([1, 2, 3, 4, 5])
	//entries()
	let arrEntries = arr.entries()
	let mapEntries = map.entries()	
	let setEntries = set.entries()
	console.log(arrEntries)			//ArrayIterator {}		
	console.log(mapEntries)			//MapIterator {["name", "web"], ["age", 18], ["hobbit", "body"]}
	console.log(setEntries)			//SetIterator {[1, 1], [2, 2], [3, 3], [4, 4], [5, 5]}

	//entries迭代器对于Array返回[index, value]、对于Map返回[key, value]、对于Set返回[key, key]
	console.log(arrEntries.next())	//Object {value: (2) [0, 1], done: false}
	console.log(mapEntries.next())	//Object {value: (2) ["name", "web"], done: false}
	console.log(setEntries.next())	//Object {value: (2) (2) [1, 1], done: false}
	//Array
	for(let item  of arrEntries){
		console.log(item);			// [0, 1] // [1, 2] // [2, 3] // [3, 4]
	}
	//Map
	for(let item  of mapEntries){
		console.log(item);			//["name", "web"] // ["age", 18] // ["hobbit", "body"]
	}
	//Set
	for(let item  of setEntries){
		console.log(item);			// [1, 1] // [2, 2] // [3, 3] // [4, 4] // [5, 5]
	}
	//解构赋值:
	for(let [key, value]  of mapEntries){
		console.log(key, value);	//name web //age 18 //hobbit body
	}	

	//keys()
	let arrkeys = arr.keys()
	let mapkeys = map.keys()
	let setkeys = set.keys()

	for(let key of mapkeys){
		console.log(key);			//name //age //hobbit
	}
	//values()
	let arrvalues = arr.values()
	let mapvalues = map.values()
	let setvalues = set.values()

	for(let value of mapvalues){
		console.log(value);			//web //18 //body
	}

	//所有的迭代器都有Symbol.iterator属性, 必须通过中括号形式访问[Symbol.iterator],返回默认的迭代器:
	for(let item of arr[Symbol.iterator]()){
		console.log(item);			//1 //2 //3 //4
	}

	//原理
	//数组中迭代器中的指针,每次指向下一个索引
	//Map、Set 会把临接链表转化成为一个单向链表,指针每次指向一个节点,指针下移

	//默认迭代器:
	for(let values of arr){	//arr[Symbol.iterator]() => arr.values()
		console.log(values);	//1 //2 //3 //4
	}

	for(let item of map){	//map[Symbol.iterator]() => map.entries()
		console.log(item); 	//["name", "web"] // ["age", 18] // ["hobbit", "body"]
	}

	for(let key of set){	//set[Symbol.iterator]() => set.keys()
		console.log(key);	//1 //2 //3 //4 //5
	}

	//对象是不可迭代的,对象没有迭代器:
	var obj = {}
	obj[Symbol.iterator]().next()	//报错obj[Symbol.iterator] is not a function

	//数组是可迭代的,数组有迭代器:
	[1][Symbol.iterator]().next()	//{value: 1, done: false}

	//真正能实现迭代的原因是因为数据是有序的，数组、Map、Set结构里的数据都是有序的
	//对象里的数据是无序的所以不可迭代

	//String、类数组
	'123'[Symbol.iterator]().next()	//{value: '1', done: false}
	//双字节词for of能遍历到,但for in却不能
	for(let s of str){
		console.log(s);
	}

	//likeArray
	function fn(){
		for(let arg of arguments[Symbol.iterator]()){
			console.log(arg);
		}
	}
</script>

---

-       生成器     	*
- yield 与 yield\* \*
-      				*

---

<script type="text/javascript">
	//生成器,Generator,用于生成迭代器,生成器是函数,要带上*,里面有yield,执行时遇到yield就停,调用next就继续执行;
	//yield 和yield* 都是配合Generator进行使用的。
	function* createIterator(){
		yield 1;
		yield 2;
		yield 3;
	}
	var i = createIterator()

	console.log(i[Symbol.iterator]())		//createIterator {<suspended>}
	console.log(i[Symbol.iterator]().next())//{value: 1, done: false}
	console.log(i.next())					//{value: 2, done: false}
	console.log(i.next())					//{value: 3, done: false}

	function* createIterator(){
		console.log('first before');
		yield 1;
		console.log('sec before');
		yield 2;
		console.log('thir before');
		yield 3;
	}
	var i = createIterator()
	console.log(i.next());					//first before //Object {value: 1, done: false}
	console.log(i.next());					//sec before   //Object {value: 2, done: false}
	console.log(i.next());					//thir before  //Object {value: 3, done: false}

	//不能当构造函数用:
	var c = new createIterator();			//报错

	function* createIterator(){
		var res;
		yield res = 1;
		console.log(res);
		yield 2;
		console.log('thir before');
		yield 3;
	}
	var i = createIterator()

	//控制台输入
	i.next()		//Object {value: 1, done: false}
	i.next()		//1	  //Object {value: 2, done: false}

	//yield可以停止for循环
	function* createIterator(arr = []){
		for(let i = 0; i < arr.length; i++){
			yield arr[i]
		}
	}
	var i = createIterator([1, 2, 2, 3]);
	//控制台输入
	i.next() 	//Object {value: 1, done: false}
	i.next() 	//Object {value: 2, done: false}
	i.next() 	//Object {value: 2, done: false}
	i.next() 	//Object {value: 3, done: false}
	i.next() 	//Object {value: undefined, done: true}

	//yield不可在普通函数中使用,只可以在带有*的函数中使用
	function* createIterator(arr = []){
		// arr.forEach(function(item){
		// 	yield item				//报错
		// }
	}	

	//有return后面的所有函数都不会执行了,也包括yield,和正常函数一样
	function* createIterator(arr = []){
		yield 1
		yield 2
		return 3
		yield 4					//不会被执行
	}
	var i = createIterator();
	//控制台输入	
	i.next()	//Object {value: 1, done: false}
	i.next()	//Object {value: 2, done: false}
	i.next()	//Object {value: 3, done: true}
	i.next()	//Object {value: undefined, done: true}

	//yield可以模拟异步加载,模拟把大象装进冰箱:
	//普通异步方式:
	function open(){
		console.log('opening')
		setTimeout(() => {
			console.log('open Refrigerator');
			load()
		}, 1000)
	}
	function load(){
		console.log('loading')
		setTimeout(() => {
			console.log('load over');
			close()
		}, 3000)
	}
	function close(){
		console.log('closing')
		setTimeout(() => {
			console.log('close Refrigerator');
		}, 1000)
	}
	//控制台输入
	open()					//依次按顺序执行各函数
---------------------------------------------------
	//使用迭代器实现(异步函数同步化书写):
	function open(){
		console.log('opening')
		setTimeout(() => {
			console.log('open Refrigerator');
			process.next()
		}, 1000)
	}
	function load(){
		console.log('loading')
		setTimeout(() => {
			console.log('load over');
			process.next()
		}, 3000)
	}
	function close(){
		console.log('closing')
		setTimeout(() => {
			console.log('close Refrigerator');
		}, 1000)
	}
	function* loadElephant(){
		yield open()
		yield load()
		yield close()
	}
	let process = loadElephant()
	process.next()			//依次按顺序执行各函数

	//yield没有返回值
	function* create(){
		var res;
		yield 2;
		res = yield 3
		yield res
	}
	let i = create()
	//控制台输入
	i.next()	//Object {value: 2, done: false}
	i.next()	//Object {value: 3, done: false}
	i.next()	//Object {value: undefined, done: false}
*************************************************************
*						yield详解							*
*************************************************************
	//yield是关键字，其语法如下：
	//[rv] = yield [expression];

	//expression：是Generator函数返回的遍历器对象调用next方法时所得到的值;
	//rv：是遍历器对象调用next方法时在next方法里传入的参数;
	
	//next函数里可以传值;next里传值后的程序执行顺序为:
	//[1]在上一个yield处传入next里的值
	//[2]执行下一个yield关键字之前的程序,
	//[3]返回紧跟yield关键字后面的数据(此时可将yield当成return来看待)

	//next方法语法如下：
	//generator.next(value)

	//value：传递给Generator函数的值，也就是上文中的rv
	//这里比较有意思的就是传递给next方法的值value，下面通过例子来看一下：

	function* _test(){
	    let value = '';
	    value = yield '1';
	    console.info(`1 value is: ${value}`);//输出1

	    value = yield '2';
	    console.info(`2 value is: ${value}`);//输出2
	    return 'over';
	}

	let _iter = _test();
	let _res = _iter.next();				//调用1,此时调用_iter.next()会有返回值,返回值就是Object {value: '1', done: false},所以此时_res就是这个返回值
	console.info(`1: ${_res.value}`);		//输出3

	_res = _iter.next('params from next');	//调用2,此时调用_iter.next('params from next')依然会有返回值,
											//返回值就是Object {value: '2', done: false},所以此时_res就是这个返回值
	console.info(`2: ${_res.value}`);		//输出4

	_res = _iter.next();					//调用3
	console.info(`3: ${_res.value}`);		//输出5

	//输出如下：

	//1: 1							//输出3(调用1调用后的返回值)
	//1 value is: params from next 	//输出1(由调用2进入_test函数执行)
	//2: 2							//输出4(调用2调用后的返回值)
	//2 value is: undefined 		//输出2(由调用3进入_test函数执行)
	//3: over 						//输出5

	//注释中标记了几个输出语句，输出1是在第2次调用next方法时执行的，可以看到，此时的value值是传递给next方法的参数，
	//但是在_test函数中可以看到value = 'params from next'，
	//所以可以理解为，在第一次执行next函数的时候，语句value = yield '1';中的yield '1'没有返回值，所以也就没有赋值给value，
	//而在第2次调用next时，才将next函数的参数赋值给value。
	
	//综上,_test函数中的value赋值是由next函数传入的参数决定的。而_test函数外的_res对象的value是由_test函数里每个yield后面的值决定的。

	function* create(){
		var res;
		console.log(7);
		yield 2;
		res = yield 3
		console.log(res)
		yield res
	}
	let i = create()
	//控制台输入
	i.next() 		//7		//Object {value: 2, done: false}
	i.next()		//Object {value: 3, done: false}
	i.next('hello')	//hello	//Object {value: "hello", done: false}	

	function* create(){
		var res = yield 1;
		yield res
	}
	let i = create();
	//控制台输入
	i.next('hi') 		//Object {value: 1, done: false},因为next内传入的值是在上一个yield位置处传入,对于首次执行的next来说没有上一个yield所以白传了。
	i.next('hello') 	//Object {value: "hello", done: false}
	i.next('see') 		//Object {value: undefined, done: true}

	//吃橘子过程: 洗手=>扒皮=>吃
	function washHands(){
		console.log('洗手中')
		setTimeout(function(){
			process.next('一双干净的手')
		},2000)
	}
	function peeling(){
		console.log('扒橘子中')
		setTimeout(function(){
			process.next('一个拔完皮的橘子')			
		},3000)
	}
	function eat(){
		console.log('吃橘子中')
		setTimeout(function(){
			process.next('真好吃')			
		},1000)
	}
	function* eatOrange(){
		let result = `${yield washHands()} --拿着-- ${yield peeling()} --边吃边说-- ${yield eat()}`
		console.log(result);
	}

	let process = eatOrange()
	process.next();
*************************************************************
*						yield*详解							*
*************************************************************
	//委托迭代器 yield*
	//yield*是表达式，因此有返回值，其语法如下：
	//yield* [[expression]];
	//expression：是可遍历对象，可以是数组，也可以是另外一个Generator函数的执行表达式
	//其实说简单点，就是将多个yield语句根据某种规则合并为一个，示例如下：

	function* g3() {
	  yield* [1, 2];
	  yield* '34';
	  yield* Array.from(arguments);
	}

	var iterator = g3(5, 6);

	console.log(iterator.next()); // {value: 1, done: false}
	console.log(iterator.next()); // {value: 2, done: false}
	console.log(iterator.next()); // {value: "3", done: false}
	console.log(iterator.next()); // {value: "4", done: false}
	console.log(iterator.next()); // {value: 5, done: false}
	console.log(iterator.next()); // {value: 6, done: false}
	console.log(iterator.next()); // {value: undefined, done: true}

	//同时遍历数组和字符串
	function* show(arr, string){
		yield* arr
		yield* string 		//遍历完数组后遍历字符串
	}

	let i = show([1, 2, 3], 'I love you')
	for(let item of i){
		console.log(item);	//依次打印 //1 //2 //3 //I //  //l //o //v //e //  //y //o //u
	}
</script>

---

-               *
-        class	*
-               *

---

<script type="text/javascript">
	//函数声明 VS class声明
	function Animal(name, age){
		this.name = name
		this.age = age
		this.showMsg = function(){
			console.log(this.name, this.age);
		}
	}
	class Person{
		constructor(name, age = 0){
			this.name = name
			this.age = age
			this.fn = function(){				//在constructor内部写的函数不会被处理成原型链上的方法,会当成私有方法
				console.log('inner')
			}
		}
		showMsg(){								//在class函数内部与constructor函数并列的函数将被处理成原型上的方法
			console.log(this.name, this.age)
		}
	}

	Person() 									//报错,通过class声明的构造函数无法执行,只能通过new关键字实例化

	console.log(typeof Animal, typeof Person);	//function function, 说明class是一个function,其实class是一个语法糖(糖衣语法)

	var p1 = new Person('lyc', 18);
	var dog = new Animal('二哈');

	console.log(p1);	//Person {name: "lyc", age: 18}  showMsg在原型上
	console.log(p1.showMsg === Person.prototype.showMsg); 				//true
	console.log(dog);	//Animal {name: "二哈", age: 0, showMsg: ƒ}	 
	console.log(dog.showMsg === Animal.prototype.showMsg); 				//false

	console.log(Person.prototype.constructor == Person);	//true
	console.log(Animal.prototype.constructor == Animal);	//true

	//对于class声明的函数来说,没有声明整提升,而是存在TDZ(暂时性死区的),所以必须要先声明后定义

	let me = new Person();		//报错,要先声明后调用
	class Person(){
		...
	}

	//可以直接实例化,同时进行传参
	let obj = new class{
		constructor(name){
			this.name = name;
		}
	}('lyc');

	//可以被迭代的类
	class IteratorArr{
		constructor(){
			this.arr = [1, 2, 3, 4, 5, 6]
		}
		//只要是迭代器就一定有[Symbol.iterator]这个暴露的接口,用于提供默认的迭代器
		//只要是能迭代的对象就一定能被for of遍历
		*[Symbol.iterator](){
			yield* this.arr[Symbol.iterator]()/ this.arr(啥也不写默认为[Symbol.iterator]())/ this.arr.keys()/ this.arr.values()/ this.arr.entries()
		}
	}
	var i = new IteratorArr()

	for(let item of i[Symbol.iterator]()){

	}
**********************************************************
	//继承,继承的目的,增加代码复用
	//ES5
	//圣杯模式继承:
	function inherit(Origin, Target) {
	    function F() {};
	    F.prototype = Origin.prototype;
	    Target.prototype = new F();
	    Target.prototype.constructor = Target;
	    Target.prototype.uber = Origin.prototype;	//Target的真正原型
	}
	//使用call和Object.create方法的继承：
	function Animal(name = 'none', age = 1){
		this.age = age
		this.name = name
		this.sleep = function(){
			console.log('animal sleep')
		}
	}
	Animal.prototype.eat = function(){
		console.log('animal eat')
	}
	Animal.prototype.drink = function(){
		console.log('animal drink')
	}
	//第一步:继承Animal函数的属性和静态方法(比如sleep方法)
	function Person(name, age){
		Animal.call(this, name, age)
	}
	//第二步:继承Animal函数的原型链上的方法(比如eat、drink方法)
	Person.prototype = Object.create(Animal.prototype)
	//第三步:更改继承函数原型上的constructor和uber
	Person.prototype.constructor = Person;
	Person.prototype.uber = Animal.prototype;	//Target的真正原型

	var p = new Person('dg', 60)

	console.log(p instanceof Person)	//true
	console.log(p instanceof Animal)	//true

	//重写Person的eat、drink方法不会影响Animal的eat、drink方法,因为不共享原型链
	Person.prototype.eat = function(){
		console.log('person eat')
	}
	Person.prototype.drink = function(){
		console.log('person drink')
	}

	//class
	//super当成函数来用代表调用父类构造函数
	//super当成对象来用代表的是父类的原型,同时super具有自动绑定this的功能,this自动绑定到子类上
	class Animal{
		constructor(name, age){
			this.age = age
			this.name = name
		}
		eat(){
			console.log('animal eat')
		}
		drink(){
			console.log('animal drink')
		}
	}
	Animal.prototype.type = 'animal'

	class Person extends Animal{		//Person继承自Animal,Person被称为子类(派生类),此时Animal是Person的父类
		constructor(name, age){
			super(name, age)			//相当于Animal.call(this, name, age),super当成函数来用时,代表调用父类的构造函数为子类实现一个对象,
										//同时super具有自动绑定this的功能,这个this会绑定到自己身上,而非父类身上。
			this.nose = 1
		}
		//重写Person下的eat方法
		eat(){
			console.log(super.type)		//'animal' ,super当成对象来用代表的是父类的原型
			console.log(super.eat())	
			//'animal eat',虽然调用的是原型上的eat方法,但依旧绑定的是子类的this,如果super.eat()函数里有console.log(this),this会指向Person{....}
			console.log('person eat')
		}
	}
	var p = new Person('dg', 60);
</script>

class内的this指向

类的方法内部如果含有this，它默认指向类的实例。但是，必须非常小心，一旦单独使用该方法，很可能报错。
<script type="text/javascript">
class Logger {
  printName(name = 'there') {
    this.print(`Hello ${name}`);
  }

  print(text) {
    console.log(text);
  }
}

const logger = new Logger();
const { printName } = logger;
printName(); // TypeError: Cannot read property 'print' of undefined
</script>
上面代码中，printName方法中的this，默认指向Logger类的实例。但是，如果将这个方法提取出来单独使用，this会指向该方法运行时所在的环境（由于 class 内部是严格模式，所以 this 实际指向的是undefined），从而导致找不到print方法而报错。

一个比较简单的解决方法是，在构造方法中绑定this，这样就不会找不到print方法了。
<script type="text/javascript">
class Logger {
  constructor() {
    this.printName = this.printName.bind(this);
  }

  // ...
}
</script>
另一种解决方法是使用箭头函数。
<script type="text/javascript">
class Obj {
  constructor() {
    this.getThis = () => this;
  }
}

const myObj = new Obj();
myObj.getThis() === myObj // true
</script>
箭头函数内部的this总是指向定义时所在的对象。上面代码中，箭头函数位于构造函数内部，它的定义生效的时候，是在构造函数执行的时候。这时，箭头函数所在的运行环境，肯定是实例对象，所以this会总是指向实例对象。

还有一种解决方法是使用Proxy，获取方法的时候，自动绑定this。
<script type="text/javascript">
function selfish (target) {
  const cache = new WeakMap();
  const handler = {
    get (target, key) {
      const value = Reflect.get(target, key);
      if (typeof value !== 'function') {
        return value;
      }
      if (!cache.has(value)) {
        cache.set(value, value.bind(target));
      }
      return cache.get(value);
    }
  };
  const proxy = new Proxy(target, handler);
  return proxy;
}

const logger = selfish(new Logger());
</script>

静态方法中的this指向
注意，如果静态方法包含this关键字，这个this指的是类，而不是实例。
<script type="text/javascript">
class Foo {
  static test1() {
    this.test2();
  }
  static test2() {
    console.log('hello');
  }
  test2() {
    console.log('world');
  }
}

Foo.test1() // hello
</script>
上面代码中，静态方法test1调用了this.test2，这里的this指的是Foo类，而不是Foo的实例，等同于调用Foo.test2。另外，从这个例子还可以看出，静态方法可以与非静态方法重名。

父类的静态方法，可以被子类继承。
<script type="text/javascript">
class Foo {
  static classMethod() {
    return 'hello';
  }
}

class Bar extends Foo {
}

Bar.classMethod() // 'hello'
</script>
上面代码中，父类Foo有一个静态方法，子类Bar可以调用这个方法。

静态方法也是可以从super对象上调用的。
<script type="text/javascript">
class Foo {
  static classMethod() {
    return 'hello';
  }
}

class Bar extends Foo {
  static classMethod() {
    return super.classMethod() + ', too';
  }
}

Bar.classMethod() // "hello, too"
</script>
实例属性的新写法
实例属性除了定义在constructor()方法里面的this上面，也可以定义在类的最顶层。
<script type="text/javascript">
class IncreasingCounter {
  constructor() {
    this._count = 0;
  }
  get value() {
    console.log('Getting the current value!');
    return this._count;
  }
  increment() {
    this._count++;
  }
}
</script>
上面代码中，实例属性this._count定义在constructor()方法里面。另一种写法是，这个属性也可以定义在类的最顶层，其他都不变。
<script type="text/javascript">
class IncreasingCounter {
  _count = 0;
  get value() {
    console.log('Getting the current value!');
    return this._count;
  }
  increment() {
    this._count++;
  }
}
</script>
上面代码中，实例属性_count与取值函数value()和increment()方法，处于同一个层级。这时，不需要在实例属性前面加上this。

这种新写法的好处是，所有实例对象自身的属性都定义在类的头部，看上去比较整齐，一眼就能看出这个类有哪些实例属性。
<script type="text/javascript">
class foo {
  bar = 'hello';
  baz = 'world';

  constructor() {
    // ...
  }
}
</script>
上面的代码，一眼就能看出，foo类有两个实例属性，一目了然。另外，写起来也比较简洁。

new.target属性
new是从构造函数生成实例对象的命令。ES6 为new命令引入了一个new.target属性，该属性一般用在构造函数之中，返回new命令作用于的那个构造函数。如果构造函数不是通过new命令或Reflect.construct()调用的，new.target会返回undefined，因此这个属性可以用来确定构造函数是怎么调用的。
<script type="text/javascript">
function Person(name) {
  if (new.target !== undefined) {
  	// Person是使用new命令调用的
    this.name = name;
  } else {
  	// Person不是使用new命令调用的
    throw new Error('必须使用 new 命令生成实例');
  }
}

// 另一种写法
function Person(name) {
  if (new.target === Person) {
    this.name = name;
  } else {
    throw new Error('必须使用 new 命令生成实例');
  }
}

var person = new Person('张三'); // 正确
var notAPerson = Person.call(person, '张三');  // 报错
</script>
上面代码确保构造函数只能通过new命令调用。

Class 内部调用new.target，返回当前 Class。
<script type="text/javascript">
class Rectangle {
  constructor(length, width) {
    console.log(new.target === Rectangle);
    this.length = length;
    this.width = width;
  }
}
var obj = new Rectangle(3, 4); // 输出 true
</script>

需要注意的是，子类继承父类时，new.target会返回子类。
<script type="text/javascript">
class Rectangle {
  constructor(length, width) {
    console.log(new.target === Rectangle);
    // ...
  }
}

class Square extends Rectangle {
  constructor(length, width) {
    super(length, width);
  }
}

var obj = new Square(3); // 输出 false
</script>
上面代码中，new.target会返回子类。

利用这个特点，可以写出不能独立使用、必须继承后才能使用的类(抽象类)。
<script type="text/javascript">
class Shape {
  constructor() {
    if (new.target === Shape) {
      throw new Error('本类不能实例化');
    }
  }
}

class Rectangle extends Shape {
  constructor(length, width) {
    super();
    // ...
  }
}

var x = new Shape();  // 报错
var y = new Rectangle(3, 4);  // 正确
</script>
上面代码中，Shape类不能被实例化，只能用于继承。
注意，在函数外部，使用new.target会报错。

私有方法和私有属性
现有的解决方案
私有方法和私有属性，是只能在类的内部访问的方法和属性，外部不能访问。这是常见需求，有利于代码的封装，但 ES6 不提供，只能通过变通方法模拟实现。

一种做法是在命名上加以区别。
<script type="text/javascript">
class Widget {

  // 公有方法
  foo (baz) {
    this._bar(baz);
  }

  // 私有方法
  _bar(baz) {
    return this.snaf = baz;
  }

  // ...
}
</script>
上面代码中，_bar方法前面的下划线，表示这是一个只限于内部使用的私有方法。但是，这种命名是不保险的，在类的外部，还是可以调用到这个方法。

另一种方法就是索性将私有方法移出模块，因为模块内部的所有方法都是对外可见的。
<script type="text/javascript">
class Widget {
  foo (baz) {
    bar.call(this, baz);
  }

  // ...
}

function bar(baz) {
  return this.snaf = baz;
}
</script>
上面代码中，foo是公开方法，内部调用了bar.call(this, baz)。这使得bar实际上成为了当前模块的私有方法。

还有一种方法是利用Symbol值的唯一性，将私有方法的名字命名为一个Symbol值。
<script type="text/javascript">
const bar = Symbol('bar');
const snaf = Symbol('snaf');

export default class myClass{

  // 公有方法
  foo(baz) {
    this[bar](baz);
  }

  // 私有方法
  [bar](baz) {
    return this[snaf] = baz;
  }

  // ...
};
</script>
上面代码中，bar和snaf都是Symbol值，一般情况下无法获取到它们，因此达到了私有方法和私有属性的效果。但是也不是绝对不行，Reflect.ownKeys()依然可以拿到它们。
<script type="text/javascript">
const inst = new myClass();

Reflect.ownKeys(myClass.prototype)
// [ 'constructor', 'foo', Symbol(bar) ]
</script>
上面代码中，Symbol 值的属性名依然可以从类的外部拿到。

私有属性的提案
目前，有一个提案，为class加了私有属性。方法是在属性名之前，使用#表示。
<script type="text/javascript">
class IncreasingCounter {
  #count = 0;
  get value() {
    console.log('Getting the current value!');
    return this.#count;
  }
  increment() {
    this.#count++;
  }
}
</script>
上面代码中，#count就是私有属性，只能在类的内部使用（this.#count）。如果在类的外部使用，就会报错。
<script type="text/javascript">
const counter = new IncreasingCounter();
counter.#count // 报错
counter.#count = 42 // 报错
</script>
上面代码在类的外部，读取私有属性，就会报错。

下面是另一个例子。
<script type="text/javascript">
class Point {
  #x;

  constructor(x = 0) {
    this.#x = +x;
  }

  get x() {
    return this.#x;
  }

  set x(value) {
    this.#x = +value;
  }
}
</script>
上面代码中，#x就是私有属性，在Point类之外是读取不到这个属性的。由于井号#是属性名的一部分，使用时必须带有#一起使用，所以#x和x是两个不同的属性。

之所以要引入一个新的前缀#表示私有属性，而没有采用private关键字，是因为 JavaScript 是一门动态语言，没有类型声明，使用独立的符号似乎是唯一的比较方便可靠的方法，能够准确地区分一种属性是否为私有属性。另外，Ruby 语言使用@表示私有属性，ES6 没有用这个符号而使用#，是因为@已经被留给了 Decorator。

这种写法不仅可以写私有属性，还可以用来写私有方法。
<script type="text/javascript">
class Foo {
  #a;
  #b;
  constructor(a, b) {
    this.#a = a;
    this.#b = b;
  }
  #sum() {
    return #a + #b;
  }
  printSum() {
    console.log(this.#sum());
  }
}
</script>
上面代码中，#sum()就是一个私有方法。

另外，私有属性也可以设置 getter 和 setter 方法。
<script type="text/javascript">
class Counter {
  #xValue = 0;

  constructor() {
    super();
    // ...
  }

  get #x() { return #xValue; }
  set #x(value) {
    this.#xValue = value;
  }
}
</script>
上面代码中，#x是一个私有属性，它的读写都通过get #x()和set #x()来完成。

私有属性不限于从this引用，只要是在类的内部，实例也可以引用私有属性。
<script type="text/javascript">
class Foo {
  #privateValue = 42;
  static getPrivateValue(foo) {
    return foo.#privateValue;
  }
}

Foo.getPrivateValue(new Foo()); // 42
</script>
上面代码允许从实例foo上面引用私有属性。

私有属性和私有方法前面，也可以加上static关键字，表示这是一个静态的私有属性或私有方法。
<script type="text/javascript">
class FakeMath {
  static PI = 22 / 7;
  static #totallyRandomNumber = 4;

  static #computeRandomNumber() {
    return FakeMath.#totallyRandomNumber;
  }

  static random() {
    console.log('I heard you like random numbers…')
    return FakeMath.#computeRandomNumber();
  }
}

FakeMath.PI // 3.142857142857143
FakeMath.random()
// I heard you like random numbers…
// 4
FakeMath.#totallyRandomNumber // 报错
FakeMath.#computeRandomNumber() // 报错
</script>
上面代码中，#totallyRandomNumber是私有属性，#computeRandomNumber()是私有方法，只能在FakeMath这个类的内部调用，外部调用就会报错。
---

-     					*
-      Promise异步编程		*
-     				*

---

<script type="text/javascript">
	// Promise 承诺
	// 回调地狱
	fn1((suc, err) => {
		fn2((suc, err) => {
			fn3((suc, err) => {
				fn4((suc, err) =>{
					fn5((suc, err) =>{
						fn6((sec, err) =>{

						})
					})
				})
			})
		})
	})
	//依赖关系:fn1想返回值需要依赖fn2执行结束,fn2想返回值需要依赖fn3执行结束
	//fn3想返回值需要依赖fn4执行结束,fn4想返回值需要依赖fn5执行结束....
	//这是一种栈结构,fn1最先执行,其次是fn2、fn3、fn4、fn5、fn6,但fn6最先结束,其次fn5、fn4、fn3、fn2、fn1
	//Promise的降维机制：
	fn1
		s1 = 'pending' => 'resolve'	new Promise()	//fn1可以改变s1的状态,fn2的执行依赖于s1的状态,每个状态都是一个Promise对象
	fn2 											//所以Promise对象其实是一个状态对象
		s2 = 'pending' => 'resolve'	new Promise()
	fn3
		s3 = 'pending' => 'resolve'	new Promise()
	fn4
		s4 = 'pending' => 'resolve'	new Promise()
	fn5
		s5 = 'pending' => 'resolve'	new Promise()
	fn6

	//Promise对象最开始生成的时候是pending
	var s1 = new Promise(function(){});
	console.log(s1);						//Promise {<pending>}

	//Promise 声明周期 pending(准备状态) => resolve/reject(最终会变为resolve(成功状态)或reject(失败状态))
	var s1 = new Promise(function(resolve, reject){
		resolve() / reject();	//执行resolve()函数,Promise对象就变为Promise {<resolved>: undefined}
								//执行reject()函数,Promise对象就变为Promise {<rejected>: undefined}
								//执行哪个函数是由自己手动调用的。
	})
	.then(function(){
		console.log('success');	//第一个函数用来处理成功状态下的回调函数	
	},function(){
		console.log('error');	//第二个函数用来处理失败状态下的回调函数
	})

	//模拟异步:
	var s1 = new Promise(function(suc, err){
		setTimeout(function(){
			suc({value: 'fn1 is finished'})	
		}, 2000)
	})

	s1.then(function(data){
		console.log('yes');	//第一个函数用来处理成功状态下的回调函数
		console.log(data);	
	},function(err){
		console.log('no');	//第二个函数用来处理失败状态下的回调函数
	})

	s1.then(function(data){
		console.log('yes');	//第一个函数用来处理成功状态下的回调函数
		console.log(data);	
	},function(err){
		console.log('no');	//第二个函数用来处理失败状态下的回调函数
	})

		//以上代码2秒后打印
		//yes	
		//Object {value: "fn1 is finished"}
		//yes	
		//Object {value: "fn1 is finished"}
		//
		//对比Ajax的好处:Ajax请求一次不可能返回两次数据,但promise可以

	//Promise就是带有状态的对象,要配合then()一起使用才行
	//模拟前面的fn1-fn6的执行:
	var s1 = new Promise(function(suc, err){
		console.log('fn1正在做');
		setTimeout(function(){					//模拟s1执行了2后执行完毕,并返回value: 'fn1 is finished'给函数fn2
			suc({value: "fn1's result"})		//fn1把"fn1's result"作为返回值传给fn2
		}, 2000)
	})

	s1.then(function(data){
		//doing something(fn2函数执行的程序)
		console.log(data);
		console.log('fn1做完了, fn2正在做')
		return new Promise((resolve, reject) => {
			setTimeout(function(){
				resolve({value: "fn2's result"})//fn2把"fn2's result"作为返回值传给fn3
			}, 2000)							//模拟fn2程序执行了2秒
		})
	},function(err){
		console.log('no');
	})
	.then(function(data){						//最重要的是Promise支持链式调用
		//doing something(fn3函数执行的程序)
		console.log(data);
		console.log('fn2做完了, fn3正在做')
		return new Promise((resolve, reject) =>{
			setTimeout(function(){
				resolve({value: "fn3's result"})//fn3把"fn3's result"作为返回值传给fn4
			}, 1000)							//模拟fn3程序执行了1秒
		})
	},function(err){})
	.then(function(){},function(){})			//fn4

	//每个Promise对象,都是由状态(resolve, reject)和返回值这两部分组成
	//对于then函数来说：每次调用Promise对象后 .then(suc, err)都会返回一个新的Promise对象,方便链式调用
	//如果显式返回Promise对象, 那么这个新的被返回的Promise对象的状态由显示的代码定义
	//如果没有显示返回Promise对象, 那么会默认的返回一个状态为<resolved>的Promise对象
	//例：
	var p = new Promise(function(resolve, reject){
		resolve(1)
	})
	//调用.then方法后返回的Promise对象：
	//状态和返回值均没显式返回:
	var p1 = p.then(data => console.log(data), err => console.log(err))
	//只要then执行,无论是否返回了promise对象,都默认返回了一个promise对象
	//控制台输入
	p 					//Promise {<resolved>: 1}, 对于手动调用的Promise函数来说,状态值和返回值都是手动设定的(这里调用了resolve函数,并且传入了参数1)
	p1					//Promise {<resolved>: undefined},默认的返回一个状态为<resolved>的Promise对象,返回值为undefined

	//状态没显式返回,但返回值显示返回了:
	var p2 = p.then(data => data, arr => console.log(err));
	//相当于
	var p2 = p.then(data => new Promise((resolve, reject) => resolve(data)), arr => console.log(err));	
	//控制台输入
	p2					//Promise {<resolved>: 1},	箭头函数data => data的意思是输入data参数, 然后return data参数	

	//显式返回状态和返回值:
	var p3 = p.then(data => new Promise((resolve, reject) => reject(data)), arr => console.log(err));
	//控制台输入
	p3 					//Promise {<rejected>: 1}
***************************************************************************

	//Promise API
	//Promise satatus
	//status: pending => resolved / rejected 状态的改变是不可逆的
	let p = new Promise((resolve, reject) => {
		resolve(1)
		reject(1)				//Promise {<resolved>: 1}
	})

	//Promise的静态方法: Promise.resolve()、Promise.reject()、Promise.all()、Promise.race()
	//原型上的方法:Promise.prototype.then、Promise.prototype.catch

	var p1 = Promise.resolve(1)	//返回一个状态为resolved,返回值为1的Promise对象
	console.log(p1)				//Promise {<resolved>: 1}
	var p2 = Promise.reject(2)	//返回一个状态为rejected,返回值为2的Promise对象
	console.log(p2)				//Promise {<rejected>: 2}

	Promise.all(PromiseArray)	//PromiseArray是由Promise对象构成的数组
	//当PromiseArray里面每个Promise对象都为resolved时会返回每个Promise对象里的返回值,形成新数组返回,
	//只要有一个Promise对象是reject状态就返回reject状态对应的返回值
	var p1 = new Promise(resolve => resolve(1))
	var p2 = Promise.resolve(2)
	var p3 = Promise.reject(3)
	var p4 = Promise.all([p1, p2])		//控制台输入p4 Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: Array[2]}
	var p5 = Promise.all([p1, p2, p3])	//控制台输入p5 Promise {<rejected>: 3}
	var p6 = Promise.all([p4, p2, p1]).then(data => console.log(data), err => console.log(err));	// [Array(2), 2, 1]

	Promise.race(PromiseArray)			//谁执行的快就返回谁

	var p1 = new Promise(resolve => setTimeout(() => resolve(1), 3000))
	var p2 = new Promise((suc, err) => {
		setTimeout(() => {
			suc(3)
		}, 2000)
	})
	var p3 = Promise.race([p1, p2]);		//Promise {<resolved>: 3} 	p2执行的快所以返回p2的状态

	//Promise.prototype.catch
	Promise.reject(3).catch(err => console.log(err))	//3
	//相当于
	Promise.reject(3).then(null, err => console.log(err))
	//语义化比较好的写法:
	Promise.reject(3).then(data, err => console.log(data)).catch(err => console.log(err))
	//由于then里只传了一个函数,第二函数没传,所以catch会自动补进来
******************************************************************************

	//Promise的应用实例:
	var num = new Promise((suc, err) => {
		var random = Math.random() * 100
		if(random > 60){
			suc(Math.floor(random))
		}else{
			err('太小了')
		}
	})
	num.then(data => console.log(`数字为${data}`), err => {console.log(err)})
*****************************************************************************

	//使用Promise(pending => resolved / rejected)封装加载图片程序:
	function loadURL(url){
		var img = new Image()
		return new Promise((resolve, reject) => {
			img.onload = function(){
				resolve(img)			//如果加载成功就执行resolve函数同时把img返回
			}
			img.onerror = function(){
				reject(`${url} is not an effective URL`)	//如果图片加载失败就执行reject函数,返回字符串
			}
			img.src = url
		})
	}
	loadURL('http://img.zcool.cn/community/01f2e65a2639a9a80120ba3890d981.jpg@2o.jpg').then(img =>　document.body.appendChild(img), err => console.log(err))
****************************************************************************

	//使用Promise封装Ajax
	let myAjax = (url, method = 'GET') => {
		return new Promise((resolve, reject) => {
			$.ajax({
				url,
				method,
				success(data){
					resolve(data)
				},
				error(err){
					reject(err)
				}
			})
		})
	}

	var ajax1 = myAjax('www.xxx.com')
		ajax1.then((data) => {}, (err) => {})
</script>

---

-              		    *
- 手动封装 Promise 源码 \*
-               	    *

---

<script type="text/javascript">
	//ES6写法
	//声明myPromise类使用箭头函数绑定子级元素里的this
	class myPromise{
		//class声明的对象里定义方法有三种形式(构造函数也一样):
		//[1]构造函数之内的方法，写在constructor方法内
		//[2]原型链上的方法 prototype, 跟constructor平级的方法
		//[3]静态方法, 跟constructor平级, 但是使用static方法声明
		constructor(fn){
			//this.fn = function(){}	[1]构造函数之内的方法
			if(typeof fn !== 'function'){
				throw TypeError(`myPromise resolver ${fn} is not a function`)
			}
			this.status = 'pending'				//初始化状态值
			this.data = undefined 				//初始化返回值
			this.resolveCBArr = []				//为了给then方法的pending状态存储成功和失败的回调函数,
			this.rejectCBArr = []				//初始化resolveCB和rejectCB数组

			let resolve = (data) => {		//resolve函数的定义,此函数需要在new Promise()后通过外部手动书写resolve()触发
				if(this.status == 'pending'){	//箭头函数具有绑定this的能力,当箭头函数被定义的时候,他的this就已经被绑定了
												//无论以后在哪里执行
					this.status = 'resolved'//this指向myPromise,箭头函数中的this是离自己最近的非箭头函数父级作用域中的this
					this.data = data
					this.resolveCBArr.forEach(fn => fn(data))
					//如果有this.resolveCB就执行,与then里的pending配合,处理then等待结束后回调的resolveCB
					//因为整个resolve函数都是由外部函数通过手动书写resolve()触发的,
					//所以,其实resolveCB回调函数也是在外部手动触发resolve()时执行的
					//这里使用了forEach方法是因为resolveCBArr里可能有多个函数,
					//所以一旦调用resolve函数,resolveCBArr数组里的函数会接连按顺序调用
				}										
			}											

			let reject = (data) => {
				if(this.status == 'pending'){
					this.status = 'rejected'
					this.data = data
					this.rejectCBArr.forEach(fn => fn(data))
				}			
			}
			fn(resolve, reject)	//让外部传入的函数fn执行,同时传入上面定义的两个函数resolve、reject作为两个实参传入fn,供fn调用
								//这句话极其重要,如果没有这句话,作为参数的fn的函数体内将无法调用resolve与reject函数。
		}
		//对于then函数
		//如果主动返回Promise对象,接下来的then所执行的函数,由返回的Promise对象状态决定
		//如果不主动返回Promise对象,系统会自动返回一个状态为resolved的Promise对象
		//then是原型上的方法
		then(resolveFn, rejectFn){ 				//只要是then方法一定返回Promise对象
			if(this.status == 'resolved'){
				let res = resolveFn(this.data)
				if(res instanceof myPromise){	//检查返回值是不是Promise对象
					return res 					//如果是就直接返回res
				}else{
					return myPromise.resolve(res) //如果不是就手动返回状态为resolved,返回值为res的Promise对象
				}
			}
			if(this.status == 'rejected'){
				let res = rejectFn(this.data)
				if(res instanceof myPromise){
					return res
				}else{
					return myPromise.resolve(res)
				}
			}
			if(this.status == 'pending'){					//如果this.status == 'pending'则需要先做等待处理,
															//等到不是pending时再执行,
															//所以要先把回调函数resolveFn和rejectFn
															//(实例化后传入的回调函数)存起来,等到可以执行时再执行
				//————————————————————1—————————————————————//
				return new myPromise((resolve, reject) => {	//返回一个Promise对象;因为无论什么状态,只要执行then函数,
															//都要返回一个Promise对象
															//这样写是不对的:this.resolveCB = resolveFn 	
															//这样传只能把函数体传入而无法传入参数,
															//而pending结束后需要将this.data作为参数传入resolveFn然后执行
															//而且这样写无法接收resolveFn函数执行后的结果;
															//所以要使用立即执行函数返回的闭包来储存完整的resolveFn,
															//以及对this.data处理后的值
															//然后再把这个整体返回给resolveCB(此时resolveCB并不执行),
															//resolveCB会等到resolve执行时才执行resolveFn
				//————————————————————2—————————————————————//
					this.resolveCBArr.push(((resolveFn) => {//使用立即执行函数给this.resolveCB回调函数赋值,
															//(resolveFn是由then函数传进来的)
															//(传入的参数resolveFn是用户传入的成功时执行的回调函数)
				//————————————————————3—————————————————————//返回的resolveCB函数体
						return () => {						//var a = (() => {return 1})() 			则a = 1;
															//var a = (() => {return () => {1}})()	则a = () => {1};
															//所以这里return后的() => {...}是返回值
							let res = resolveFn(this.data)	//如果返回的resolveCB函数被执行了,则这句话就会被执行,
															//res被赋值为resolveFn(this.data)
															//this始终指向最开始的new myPromise(() => {})对象
															//下面代码中的的res.then(resolve, reject)或resolve(res)
															//才是最终执行完resolveCB所返回的Promise对象
							if(res instanceof myPromise){	//如果res是Promise对象,就把resolve和reject函数都做为实参
															//传到res.then(resolveFn, rejectFn)上
								res.then(resolve, reject)	//然后then会根据真正resolveCB执行时this.status的状态来确定
															//是执行resolve函数,返回状态为resolved,
															//值为this.data的Promise对象
															//还是执行reject函数,返回状态为rejected,
															//值为this.data的Promise对象
															//最后当执行let res = resolveFn(this.data)语句时,
															//res一定是Promise对象,所以就直接被return了
							}else{
								resolve(res)				//调用resolve(res),会返回一个状态为resolved,
															//值为res的Promise对象
							}
						}
				//———————————————————3——————————————————————//
					})(resolveFn))							//箭头函数的立即执行函数:let fn = (name => name)('lyc');
				//———————————————————2——————————————————————//
					this.rejectCBArr.push(((rejectFn) => {
						return () => {
							let res = rejectFn(this.data)
							if(res instanceof myPromise){
								res.then(resolve, reject)
							}else{
								resolve(res)
							}
						}
					})(rejectFn))
				})
				//———————————————————1——————————————————————//
			} 
		}
		//在构造函数中声明静态方法(仅限于方法,无法声明属性)
		//resolve与reject是静态方法	[3]静态方法
		static resolve(data){
			return new myPromise((resolve, reject) => resolve(data))
		}
		static reject(data){
			return new myPromise((resolve, reject) => reject(data))
		}
	}

	//试验除了pending时执行then的代码
	var p1 = new myPromise(function(suc, err) {suc('strong')})
	p1.then(data => console.log(data + ' suc'), err => console.log(err + ' err'))
	  .then(data => console.log(data + ' undefined'))

	var p2 = myPromise.resolve('1');
	console.log(p2) 

	//试验pending状态时执行then的代码
	//试验1
	var p1 = new myPromise((resolve, reject) => {
		setTimeout(function(){
			resolve('strong')		//2秒后执行resolve('strong')函数,所以2秒后返回一个状态为resolved,值为strong的Promise对象
		}, 2000)
	})
	p1.then(data => console.log(data))

	//试验2
	var p2 = new myPromise((resolve, reject) => {
		setTimeout(function(){
			reject('strong')
		}, 2000)
	})

	p2.then(null, data => console.log(data))	//2秒后同时打印	strong
	p2.then(null, data => console.log(data))	//				strong
	p2.then(null, data => {
		console.log(data)						//				strong
		return new myPromise((suc, err) => {
			setTimeout(function(){
				err(2234)						
			}, 2000)
		})
	})
	.then(null, data => {
 	       console.log(data)					//再过2秒打印	2234
		return new myPromise((suc, err) => {
			setTimeout(function(){
				suc('Winner is me')
			}, 2000)
		})	 	
	 })
	.then(data => console.log(data))			//再过2秒打印	'Winner is me'

	//原生Promise对象与then执行顺序:
	let p = new Promise((suc, err) => {
		console.log(1)
		suc(2)
	}).then(data => console.log(data))
	console.log(3)
	//1 //3 //2

	//自己封装的myPromise与then执行顺序:
	let p = new myPromise((suc, err) => {
		console.log(1)
		suc(2)
	}).then(data => console.log(data))
	console.log(3)
	//1 //3 //2
	//之所以顺序不是	//1 //2 //3
	//是因为在myPromise内部的构造函数里声明reject和reject函数时使用了setTimeout(() => {}, 0)
</script>

码元与码点：
es6为正则表达式添加了一个flag：u,如果添加了该配置，则匹配时使用码点匹配
<script>
	const text = "𠮷";		// 占用了两个码元（32位）
	console.log("字符串长度：", text.length);	// 2,获取的是码元的长度
	console.log("使用正则测试：", /^.$/.test(text));	// false,因为正则测试的是码元，text字符实际有2个码元，而正则匹配1个任意码元，所以为false
	console.log("使用正则测试：", /^.$/u.test(text));	// 使用码点匹配
	console.log("得到第一个码元：", text.charCodeAt(0));
	console.log("得到地二个码元：", text.charCodeAt(1));
	console.log("得到地一个码点：", text.codePointAt(0));

	/**
	 * 判断字符串char,是不是32位的
	 */
	function is32bit(char){
		// 如果码点大于了16位二进制的最大值，则其是32位的
		return char.codePointAt(0) > 0xffff;
	}
	/**
	 * 得到一个字符串码点的真实长度
	 */
	function getLengthOfCodePoint(str){
		let len = 0;
		for(let i = 0; i < str.length; i++){
			// i在索引码元
			if(is32bit(str, i)){
				// 当前字符串，在i这个位置，占用了两个码元
				i++;
			}
			len++
		}
		return len;
	}
</script>
更多的字符串api
<script>
	const text = "川哥是狠人";
	// includes("目标字符", '起始位置')
	console.log("从第二个位置开始是否包含“狠”字：", text.includes("狠", 1));
	// startsWith
	console.log("是否以“川哥”开头：", text.startsWith("川哥", 0));
	// endsWith
	console.log("是否以“狠人”结尾：", text.endsWith("狠人", 0));
	// repeat
	console.log("重复4次：", text.repeat(4));
</script>
正则中的粘连标记
标记名：y
含义：匹配时，完全按照正则对象中的lastIndex位置开始匹配，并且匹配的位置必须在lastIndex位置(类似于正则中的"^"（以……开头）)。
<script>
	const text = "Hello World!!!";
	const reg = /W\w+/y;
	reg.lastIndex = 3;
	console.log("reg.lastIndex:", reg.lastIndex)
	console.log(reg.test)	// 从3号位匹配正则
</script>

模板字符串的标记
在模板字符串书写之前，可以加上标记：
格式：标记`模板字符串`
标记是一个函数，函数参数如下：
1.参数1：被插值分割的字符串数组
2.后续参数：所有的插值
<script>
	let love1 = '秋葵';
	let love2 = '香菜';
	let text = safe`川哥喜欢${love1}，川哥也喜欢${love2}。`;
	// 相当于；
	// let text = safe(["川哥喜欢", "，川哥也喜欢", "。"], "秋葵", "香菜")
	function safe(parts){
		console.log(parts)		// ["川哥喜欢", "，川哥也喜欢", "。"]
		console.log(arguments)	// Arguments(3){ 0: ["川哥喜欢", "，川哥也喜欢", "。"], 1: "秋葵", 2: "香菜" }
		const values = Array.prototype.slice.apply(arguments).slice(1) 
		console.log(values)	// ["秋葵", "香菜"]
		// 就算最后一个参数为空字符串也会传空字符串，所以一定会有parts.length = values.length + 1
		// 模拟字符串拼接方法
		let str = "";
		for(let i = 0; i < values.length; i++){
			// 数据劫持处理
			const v = values[i].replace(/</g, "&lt;").replace(/>/g, "&gt;");
			str += parts[i] + v;
			if(i === values.length - 1){
				str += parts [i + 1];
			}
		}
		return str
	}
</script>
强制字符传中没有转义符
<script>
	let text = String.raw`abc\t\nbcd`;
	console.log(text)	// 打印'abc\t\nbcd'
</script>

严格模式下形参与arguments是脱离的，非严格模式下是不脱离的
只要给函数加上了参数默认值，该函数会自动变为严格模式下的规则：arguments和形参脱离
形参和es6中的let或const声明一样，具有作用域，并且根据参数的声明顺序，存在暂时性死区。
<script>
	function test(a = b, b){
		console.log(a, b);
	}
	function test2(a, b){
		let a = 2;	// 错误，函数内不能重复定义形参变量
	}
	test(undefined, 2)	// 报错，在b声明之前使用了b
		function test(a = b, b){
		console.log(a, b);
	}
	test(null, 2)	// 不报错，因为传null也算传值了
</script>

arguments的缺陷：
1.如果和形参配合使用，容易导致混乱
2.从语义上，使用arguments获取参数，由于形参缺失，无法从函数定义上理解函数的真实意图
es6的剩余参数专门用于收集末尾的所有参数，将其放置到一个形参数组中
细节：一个函数仅能出现一个剩余参数;一个函数，如果有剩余参数，剩余参数必须是最后一个参数
<script>
	function sum(...args){
		// args收集了所有的参数，形成一个数组
	}
</script>

扩展运算符
给obj1赋值为obj2，并改变某些值
<script>
	const obj1 = {
		name: "111",
		age: 18,
		love: ['222', '333', '444'],
		a:{
			b:{
				c:1
			}
		}
	}
	// 浅克隆到obj2
	const obj2 = {
		...obj1,
		name: '555'
	}
	// 想改成深克隆
	const obj3 = {
		...obj1,
		love: [...obj1.love],
		a:{
			...obj1.a
		}
	}
	// 此时name: '555'会覆盖name: "111"
	// 该方法为浅克隆 
	console.log(obj1.a === obj2.a)	// true
</script>
使用剩余参数实现柯里化
<script>
	function cal(a,b,c,d){
		return a + b * c - d;
	}
	// curry: 柯里化，用户固定某个函数的前面的参数，得到一个新的函数，新的函数调用时，接收剩余的参数
	function curry(func, ...args){
		return function(...subArgs){
			const allArgs = [...args, subArgs]
			if (allArgs.length >= func.length) {
				// 参数够了，func.length表示func需要的形参个数
				return func(...allArgs)
			}else{
				// 参数不够继续固定
				return curry(func, ...allArgs)
			}
		}
	}
	const newCal = curry(cal, 1, 2);

	console.log(newCal(3, 4))	// 1+2*3-4
	console.log(newCal(4, 5))	// 1+2*4-5
	console.log(newCal(5, 6))	// 1+2*5-6
	console.log(newCal(6, 7))	// 1+2*6-7

	const newCal2 = newCal(8)

	console.log(newCal2(9))	// 1+2*8-9
</script>

判断是否使用new调用函数
<script>
	function Person(firstName, lastName){
		// 判断是否是使用new的方式来调用的函数
		// 过去的判断方式
		if(!(this instanceof Person)){
			throw new Error ("该函数没有使用new来调用")
		}
		this.firstName = firstName;
		this.lastName = lastName;
		this.fullName = `${firstName} ${lastName}`
	}
	const p1 = new Person("li", "yichuan")
	console.log(p1) // 打印由Person构造函数产生的实例

	const p2 = Person("li", "yichuan")
	console.log(p2)	// 该函数会进入到if(!(this instanceof Person))判断中报错

	const p3 = Person.call(p1, "li", "yichuan")
	console.log(p3) // 该函数会不会进入if(!(this instanceof Person))判断，又因为只是调用了Person函数,所以会返回undefined
</script>

es6为了明确函数的双重用途
es6提供了一个特殊的api,可以使用该api在函数内部，判断该函数是否使用了new来调用
```js
new.target
// 该表达式。得到的是：如果没有使用new来调用函数，则返回undefined
// 如果使用new调用函数，则得到的是new关键字后面的函数
```
<script>
	function Person(firstName, lastName){
		if(new.target === undefined){
			throw new Error("该函数没有使用new来调用")
		}
	}
	const p1 = new Person("li", "yichuan")      // 不进入if(new.target === undefined)
	const p2 = Person("li", "yichuan")			// 进入if(new.target === undefined)
	const p3 = Person.call(p1, "li", "yichuan") // 进入if(new.target === undefined)
</script>

回顾：this指向
1.通过对象调用函数，this指向对象
2.直接调用函数，this指向全局对象
3.如果通过new调用函数，this指向新创建的对象
4.如果通过对apply、call、bind调用函数，this指向指定的数据
5.如果是DOM事件函数，this指向事件源
**this的指向由如何使用函数决定，而与如何定义无关**
**箭头函数是一个函数表达式，而不是函数声明**

**箭头函数的函数体中的this,取决于箭头函数定义的位置的this指向，而与如何调用无关**
理论上，任何使用函数表达式的场景都可以使用箭头函数
箭头函数中，不存在this、arguments、new.target，如果使用了，则使用的是函数外层的对应的this、arguments、new.target
箭头函数没有原型，所以箭头函数不能作为构造函数使用
**应用场景**
1.临时性使用的函数，并不会刻意的调用它，比如：
	1.事件处理函数
	2.异步处理函数
	3.其他临时性的函数
2.为了绑定外层this的函数，对象里面的属性为函数，且需要调用函数，出现了this指向问题
3.在不影响其他代码的情况下，保持代码的简洁，最常见的，数组方法中的回调函数
4.尽量不要使用箭头函数的地方：对象里面的属性为函数，不要使用箭头函数给这个属性赋函数而是要用function

<script>
	const obj = {
		count: 0,
		print:()=>{
			console.log(this)
		}
	}
	obj.print();	// window

	const func = () => {
		console.log(this)
		console.log(arguments)
	}
	const obj = {
		method: func,
		method2: function(){
			const func = () =>{
				console.log(this)
				console.log(arguments)
			}
			func()
		}
	}
	obj.method(123)	// window
					// arguments is not defined
	obj.method2(234)// obj
					// Arguments[234]

	const numbers = [3, 7, 78, 3, 5, 345];
	const result = numbers.filter(num => num % 2 !== 0).map(num => num * 2).reduce((a, b) => a + b)
	console.log(result)	// 726
</script>
对象速写
<script>
	const prop1 = "name";
	const prop2 = "age";
	const prop3 = "say"
	const user = {
		[prop1] : "li",
		[prop2] : "18",
		[prop3](){
			console.log(this[prop1], this[prop2])
		}
	}
	user[prop3]()		// li 18
	console.log(user)	// {name: "li", age: "18", say: ƒ}
</script>

Object的新增API
1.Object.is 用于判断两个数据是否相等，基本上跟严格相等（===）是一致的，除了以下两点：
1）NaN和NaN相等  2）+0和-0不相等

2.Object.getOwnPropertyNames的枚举顺序
Object.getOwnPropertyNames方法之前就存在，只不过，官方没有明确要求如何排序，完全由浏览器厂商决定
es6规定了该方法返回的数组的排序方式如下：
先排数字，并按照升序排序
再排其他，按照书写顺序排序

3.Object.setPrototypeOf
该函数用于设置某个对象的隐式原型
比如：Object.setPrototypeOf(obj1, obj2)
相当于：```obj1.__prototype__ = obj2 ```

面向对象简介
面向对象：一种编程思想，跟具体的语言无关
对比面向过程：
面向过程：思考的切入点是功能的步骤
面向对象：思考的切入点是对象的划分

**类：构造函数的语法糖**
传统的构造函数的问题
1.属性和原型方法定义分离，降低了可读性
2.原型成员可以被枚举
3.默认情况下，构造函数仍然可以被当作普通函数使用

<script>
	// 面向对象中，将下面对一个对象的所有成员的定义，统称为类
	// 构造函数，构造器
	function Animal(type, name, age, sex){
		this.type = type;
		this.name = name;
		this.age = age;
		this.sex = sex;
	}
	// ...中间可能有一千行代码,导致构造器和原型分离，但他们其实应该是一个整体...
	// 定义实例方法（原型方法）
	Animal.prototype.print = function(){
		console.log(`【种类】：${this.type}`);
		console.log(`【名字】：${this.name}`);
		console.log(`【年龄】：${this.age}`);
		console.log(`【性别】：${this.sex}`);
	}
	const a = new Animal("狗", "旺财", 3, "男");
	a.print();

	for(const prop in a){
		console.log(prop)	// 会把印原型上的print属性打印出来
	}

	// es6语法
	class Animal {
		// 构造器
		constructor(type, name, age, sex){
			this.type = type;
			this.name = name;
			this.age = age;
			this.sex = sex;
		}
		// 原型
		print(){
			console.log(`【种类】：${this.type}`);
			console.log(`【名字】：${this.name}`);
			console.log(`【年龄】：${this.age}`);
			console.log(`【性别】：${this.sex}`);
		}
	}
	const a = new Animal("狗", "旺财", 3, "男");
	for(const prop in a){
		console.log(prop)	// 不会打印print属性
	}
</script>

类的特点
1.类声明不会被提升，与let和const一样，存在暂时性死区
2.类中的所有代码均在严格模式下执行
3.类的所有方法都是不可枚举的
4.类的所有方法都无法被当作构造函数使用
5.类的构造器必须使用new来调用
<script>
	class Animal {
		// 构造器
		constructor(name){
			this.name = name;
			this.say = function(){
				console.log(1)
			}
		}
		// 原型
		print(){
			console.log(`【名字】：${this.name}`);
		}
		// age: '18'	// 报错，类里面不可以出现属性，只能出现方法
	}
	const a = new Animal("旺财");
	for(const prop in a){
		console.log(prop)	// 不会打印print属性,会打印name和say
	}
</script>

类的set与get
使用get和set控制的属性不在原型上
<script>
	class Animal {
		// 静态成员，类自己的属性，相当于所有实例的公共属性,无法通过实例访问，只能通过类来进行访问
		static type = 'human';	// 类型为人
		static alive = true;	// 活着为true
		static method(){}
		// 构造器
		constructor(name, age){
			this.name = name;
			this.age = age
			// 相当于
			Object.defineProperty(this, "age", {
				set(val){

				},
				get(){

				}
			})
		}
		// 创建一个age属性，并给它加上getter,读取该属性时，会运行该函数
		get age(){
			return this._age + '岁';
		}
		// 创建一个age属性，并给他加上setter，给该属性赋值时，会运行该属性
		set age(age){
			if (typeof age !== "number"){
				throw new TypeError("age property must be a number")
			}
			if(age < 0){
				age = 0;
			}else if(age > 1000){
				age = 1000
			}
		}
		
	}
	const a = new Animal("旺财");
	for(const prop in a){
		console.log(prop)	// 不会打印print属性,会打印name和say
	}
</script>

类的属性初始化器
1).使用static的字段初始化器，添加的是静态成员
2).没有使用static的字段初始化器，添加的成员位于对象上
3).箭头函数在字段初始化器位置上，指向当前对象
<script>
	class Test{
		a = 1
		constructor(){
			// 相当于
			// this.print1 = () => {
			// 	console.log(this.a)
			// }
			this.a = 123;
		}
		print1 = () =>{
			console.log(this.a)
		}
		print2(){
			console.log(this.a)
		}
	}
	const t1 = new Test();
	const t2 = new Test();
	console.log(t1.print1 === t2.print1)	// fasle	生成的print1不在原型上，而是在实例上
	console.log(t1.print2 === t2.print2)	// true
	t1.print1();			// 123
	t1.print2();			// 123
	const p1 = t1.print1;
	const p2 = t1.print2;
	p1()					// 123
	p2()					// 报错 Cannot read property 'a' of undefined
</script>

类的继承
新的关键字：
extend:继承，用于类的定义
super: 
	1.直接当作函数调用，表示父类构造函数
	2.如果当作对象使用，则表示父类的原型
注意：es6要求，如果定义了constructor,并且该类是子类，则必须在constructor的第一行手动调用父类的构造函数，否则会报错
	如果子类不写constructor，则会有默认的构造器，该构造器需要的参数和父类一致，并且自动调用父类的构造器

<script>
	class Animal {
		constructor(type, name, age, sex){

		}
		print(){

		}
	}
	class Dog extends Animal{
		// Dog的原型是Animal
		constructor(name, age, sex){
			super("犬类", name, age, sex)
			// 子类特有的属性
			this.abc = 123
		}
		print(){
			// 调用父类的print
			super.print();
			// 自己的代码
			console.log(1)
		}
	}
	const d = new Dog("旺财", 3, "公")
</script>
【冷知识】
用JS制作抽象类
	抽象类：一般是父类，不能通过该类直接创建对象
正常情况下，this的指向：this始终指向具体的类的对象，this指的是运行时的状态

<script>
	// 抽象类：
	// 下面的代码逻辑有误(错误的原因是不应该由Animal类直接创建‘旺财’，而是应该由狗类创建)
	const a = new Animal("犬类", '旺财', 3, "公")

	// 防止直接由创建实体类：
	class Animal{
		constructor(type, name, age, sex){
			if(new.target === Animal){
				throw new TypeError("你不能直接创建Animal的对象,应该通过子类创建")
			}
			this.type = type;
			this.name = name;
			this.age = age;
			this.sex = sex;
		}
	}
</script>

解构
使用es6的一种语法规则，将一个对象或数组的某个属性提取到某个变量中
<script>
	const user = {
		name: 'lyc',
		age: 18,
		sex: 'male'
	}
	// 先定义4个变量：name, age, gener, adress
	// 再从对象user中读取同名属性赋值（其中gender读取的是sex属性，默认值为男），adress默认值为‘hld’
	const {name, age, sex: gener = '男', adress = 'hld'} = user
	console.log(name, age, gener, adress)	// lyc 18 male hld
</script>
数组解构
<script>
	const nmbers = ["a", "b", "c", "d"]
	// 以下3种写法意思是一样的
	// 1
	const {
		0: n1,
		1: n2
	} = numbers;
	// 2
	const n1, n2;
	([n1, n2] = numbers);
	// 3
	const [n1, n2] = numbers
</script>
a,b互换
<script>
	let a = 1, b = 2;
	[b, a] = [a, b]
	console.log(a, b)	// 2, 1
</script>
参数解构
<script>

	function ajax({
		method = "get",
		url = "/"
	} = {}){
		// 如果ajax不传任何东西，则解构会报错，所以要传入{}作为默认值
		console.log(method, url)
	}
	ajax()
</script>

符号
符号是es6新增的一个数据类型，他通过使用函数```Symbol(符号描述)```来创建
符号设计的初衷，是为了给对象设置私有属性
私有属性：只能再对象内部使用，外面无法使用
**每次调用Symbol函数得到的符号永远不相等，无论符号名是否相同**
符号可以作为对象的属性名存在，这种属性称之为符号属性
符号属性不能被枚举，因此在for-in循环中无法读取到符号属性，Object.keys方法也无法读取到符号属性
Object.getOwnPropertyNames尽管可以得到所有无法枚举的属性，但是仍然无法读取到符号属性，但可以使用getOwnPropertySymbols获取
使用符号创建类的私有属性
<script>
	const Hero = (() => {
		const getRandom = Symbol();
		return class {
			constructor(attack){
				this.attack = attack;
			}
			gongji(){
				// 伤害：攻击力*随机数(0.8~1.1)
				const dmg = this.attack * this[getRandom](0.8, 1.1);
				console.log(dmg);
			}
			[getRandom](min, max){
				return Math.random() * (max - min) + min;
			}
		}
	})();
	const h = new Hero(10)
	const sybs = Object.getOwnPropertySymbols(Hero.prototype)	// 能获取到symbol
	console.log(sybs)
</script>
共享符号
根据某个符号描述(符号名称)能够得到同一个符号
<script>
	const syb1 = Symbol.for("abc")
	const syb2 = Symbol.for("abc")
	const obj = {
		a: 1,
		b: 2,
		[Symbol.for("c")]: 3
	}
	console.log(obj[Symbol.for("c")])		// 3
	console.log(syb1 === syb2)				// true
</script>
手动模拟共享符号
<script>
	const SymbolFor = (()=>{
		const global = {};	// 用于记录有哪些共享符号
			return function (name){
				console.log(global)
				if(!global[name]){
					global[name] = Symbol(name)
				}
				console.log(global);
				return global[name];
			}
	})();
	const syb1 = SymbolFor("abc");
	const syb2 = SymbolFor("abc");
	console.log(syb1 === syb2)	// true
</script>
具名符号/知名符号
es6延续了es5的思想：减少魔法，暴露内部实现，因此，es6用知名符号暴露了某些场景的内部实现
<script>
	// Symbol.hasInstance会影响instanceof方法
	function A(){}
	const obj = new A();
	// 以下两种写法意思一样
	console.log(obj instanceof A)
	console.log(A[Symbol.hasInstance](obj));
	// 所以手动可以改写 instanceof方法,但是必须要通过Object.defineProperty方法实现
	// Object.defineProperty(A, Symbol.hasInstance, {
	// 	value(obj){
	// 		console.log("判定obj")
	// 		return false;
	// 	}
	// })

	// Symbol.toPrimitive会影响类型转换的结果
	class Temperature {
		constructor(degree){
			this.degree = degree
		}
		[Symbol.toPrimitive](type){
			if(type === "default"){
				return this.degree + "摄氏度"
			}else if(type === "number"){
				return this.degree;
			}else if(type === "string"){
				return this.degree + "℃"
			}
		}
	}

	const t = new Temperature(30);
	console.log(t + "!");	// 30摄氏度
	console.log(t / 2);		// 15
	console.log(String(t)); // 30℃

	// Symbol.toStringTag 会影响toString方法
	class Person{
		[Symbol.toStringTag] = "Person"
	}
	const p = new Person()
	console.log(Object.prototype.toString.apply(p))	// [objcet Person]
</script>

浏览器宿主环境中包含5个线程：
1.JS引擎：负责执行执行栈的最顶部代码
2.GUI线程：负责渲染页面
3.事件监听线程：负责监听各种事件
4.计时线程：负责计时
5.网络线程：负责网络通信

当上面的线程发生了某些事情，如果该线程发现，这件事情有处理程序，它会将该处理程序加入一个叫做事件队列的内存。当JS引擎发现，执行栈中已经没有了任何内容后，会将事件队列中的第一个函数加入到执行栈中执行。
JS引擎对事件队列的取出执行方式，以及与宿主环境的配合，称之为事件循环。

事件队列在不同的宿主环境中有所差异，大部分宿主环境会将事件队列进行细分。在浏览器中，事件队列分为两种：

-1.宏任务（队列）：macroTask,计时器结束的回调、事件回调、http回调等等绝大部分异步函数进入宏队列
-2.微任务（队列）：MutationObserver,Promise产生的回调进入微队列

MutationObserver用于监听某个DOM对象的变化

当执行栈清空时，JS引擎首先会将微任务中的所有任务依次执行结束，如果没有微任务，则执行宏任务。
<button id="btn">点击</button>
<script>
	let count = 1;
	const ul = document.getElementById("container");
	document.getElementById("btn").onclick = function A() {
		setTimeout(function C() {
			console.log("添加了一个li")
		}, 0);
		var li = document.createElement("li")
		li.innerText = count++;
		ul.appendChild(li);
	}
	// 监听ul
	const observer = new MutationObserver(function B() {
		//当监听的dom元素发生变化时运行的回调函数
		console.log("ul元素发生了变化")
	})
	//监听ul
	observer.observe(ul, {
		attributes: true, //监听属性的变化
		childList: true, //监听子元素的变化
		subtree: true //监听子树的变化
	})
	//取消监听
	// observer.disconnect();

	// 先打印位于微队列里的“ul元素发生了变化”后打印位于宏队列的“添加了一个li”
</script>

promise细节
1.未决阶段的处理函数是同步的，会立即执行
2.thenable和catchable函数是异步的，就算是立即执行，也会加入到事件队列中等待执行，并且，加入的队列是微队列
3.pro.then可以只添加thenable函数，pro.catch可以单独添加catchable函数
4.在未决阶段的处理函数中，如果发生未捕获的错误，会将状态推向rejected，并会被catchable捕获
5.一旦状态推向了已决阶段，无法再对状态做任何更改
6.**Promise并没有消除回调，只是让回调变得可控**

在ajax中使用promise
<script>
	const pro = new Promise((resolve, reject) => {
		ajax({
			url: "./url",
			success(data){
				resolve(data);
			},
			error(err){
				reject(err);
			}
		})
	})
</script>
一般会以这种方式处理异步：
<script>
	function example(god){
		return new Promise((resolve, reject) => {
			console.log('common extention')
			setTimeout(() => {
				if(Math.random() < 0.5){
					resolve(true)
				}else{
					resolve(false)
				}
			}, 3000)
		})
	}
	const pro = example('1')
	pro.then(result => {
		console.log(result)
	})
</script>

Promose的串联
当后续的promise需要用到之前的promise的处理结果时，需要promise的串联
promise对象中，无论是then方法还是catch方法，他们都具有返回值，返回的是一个全新的promise对象，他的状态满足下面的规则：
1.如果当前的Promise是未决的，得到的新的promise是挂起状态
2.如果当前的promise是已决的，会运行响应的后续处理函数，并将后续处理函数的结果（返回值）作为resolved状态数据，应用到新的Promise中;如果后续处理函数发生错误，则把返回值作为rejected状态数据，应用到新的promise中
**后续的promise一定会等到前面的promise有了后续处理结果后，才会变成已决状态**
3.then返回的promise对象，一开始一定是挂起状态，因为then返回的promise对象的状态是由写在then里的函数决定的，而非由上一个promise状态决定
4.如果前面的Promise的后续处理，返回的是一个Promise，则返回的新的Promise状态和后续处理返回的Promise状态保持一致。
<script>
	const pro1 = new Promise((resolve, reject) => {
		resolve(1)
	})
	console.log(pro1)	// pro1的状态: resolved,因为promise未决阶段的处理函数是同步的，会立即执行，所以立即执行了resolve(1)
	const pro2 = pro1.then(result => result * 2)
						// 写在then里的函数是异步函数 result => result * 2，需要放入到事件队列里
						// 一定要注意写在new Promise（未决阶段）的函数体里的函数，与写在then的函数体里的函数不是一样的执行逻辑
						// promise（未决阶段）的函数是同步的，写在then里的是异步的,pro2的状态是由写在then里的函数result => result * 2决定的，而不是由pro1决定的，由于写在then里的函数result => result * 2刚开始处于挂起状态，所以pro2是pending状态
	console.log(pro2)	// pro2类型：Promise对象
						// pro2的状态：pending
</script>
循环使用promise
<script>
	function biaobai(god) {
		return new Promise(resolve => {
			console.log(`邓哥向${god}发出了表白短信`);
			setTimeout(() => {
				if (Math.random() < 0.3) {
					//女神同意拉
					resolve(true)
				} else {
					//resolve
					resolve(false);
				}
			}, 500);
		})
	}
	const gods = ["女神1", "女神2", "女神3", "女神4", "女神5"];
	let pro;
	for (let i = 0; i < gods.length; i++) {
		if (i === 0) {
			pro = biaobai(gods[i]);
		}
		pro = pro.then(resp => {
			if (resp === undefined) {
				return;
			} else if (resp) {
				console.log(`${gods[i]}同意了`)
				return;
			} else {
				console.log(`${gods[i]}拒绝了`)
				if (i < gods.length - 1) {
					return biaobai(gods[i + 1]);
				}
			}
		})
	}
</script>
promise.all
<script>
	function getRandom(min, max) {
		return Math.floor(Math.random() * (max - min)) + min;
	}
	const proms = [];
	for (let i = 0; i < 10; i++) {
		proms.push(new Promise((resolve, reject) => {
			setTimeout(() => {
				if (Math.random() < 0.5) {
					console.log(i, "完成");
					resolve(i);
				} else {
					console.log(i, "失败")
					reject(i);
				}
			}, getRandom(1000, 5000));
		}))
	}
	//等到所有的promise变成resolved状态后输出: 全部完成
	const pro = Promise.all(proms)
	pro.then(datas => {
		console.log("全部完成", datas);
	})
	pro.catch(err => {
		console.log("有失败的", err);
	})
	console.log(proms);
</script>
promise.race
<script>
	function getRandom(min, max) {
		return Math.floor(Math.random() * (max - min)) + min;
	}
	const proms = [];
	for (let i = 0; i < 10; i++) {
		proms.push(new Promise((resolve, reject) => {
			setTimeout(() => {
				if (Math.random() < 0.5) {
					console.log(i, "完成");
					resolve(i);
				} else {
					console.log(i, "失败")
					reject(i);
				}
			}, getRandom(1000, 5000));
		}))
	}
	//等到所有的promise变成resolved状态后输出: 全部完成
	const pro = Promise.race(proms)
	pro.then(data => {
		console.log("有人完成了", data);
	})
	pro.catch(err => {
		console.log("有人失败了", err);
	})
	console.log(proms);
</script>
如果对于返回的promise对象的状态没有处理函数，就把该状态的数据交给下个有这个状态处理函数的地方
<script>
	new MyPromise(resolve => {
		setTimeout(()=>{
			resolve(1);		// 返回了resolved状态
		}, 1000)
	}).catch(err => {		// 但是没有resolved状态的处理函数，就向下抛
		console.log(err)
	}).then(data => {		// 抛到这里后有resolved状态的处理函数，就在这处理
		console.log(data)
	})
</script>

##async和await
async和await是es2016能增的两个关键字，他们借鉴了es2015中生成器在实际开发中的应用目的是简化promise api的使用，并非是替代promise。

##async
目的是简化在函数的返回值中对promise的创建
async用于修饰函数（无论是函数字面量还是函数表达式），放置在函数最开始的位置，被修饰函数的返回结果一定是promise对象
async函数里无法模拟setTimeout返回promise对象的函数
<script>
	async function test(){
		console.log(1);
		return 2
	}
	// 等效于
	function test(){
		return new Promise((resolve, reject)=>{
			console.log(1);
			resolve(2)
		})
	}
</script>
改造settimeout函数
<script>
	function delay(duration){
		return new Promise((resolve, reject)=>{
			setTimeout(()=>{
				resolve();
			},duration);
		})
	}
	// 使用改造后的delay函数
	async function biaobai(god){
		console.log(`邓哥向${god}发出了表白短信`);
		await delay(1000);
		return Math.random() < 0.3
	}
</script>
await
await关键字必须出现在async函数中
await用在某个表达式之前，如果表达式是一个Promise,则得到的是thenable中的状态数据
如果await的表达式不是promise，则会将其使用Promise.resolve包装后按照规则运行
<script>
	async function test1(){
		console.log(1);
		return 2
	};
	// await
	async function test2(){
		const result = await test1();
		console.log(result);
		return '111'
	}
	// 等效于
	async function test2(){
		return new Promise((resolve, reject) => {
			test1().then(data => {
				const result = data;
				console.log(result);
				resolve('111')
			})
		})
	}

	// 请求信息
	async function getTeacher(){
		const students = await ajax({
			url: "./data/student.json"
		}) 
		console.log(students)
		// 等效于
		ajax({
			url: "./data/students.json"
		}).then(resp=>{
			const students = resp;
			console.log(students)
		})
	}
	getTeacher()

	// 女神表白
	async function biaobaiAll(){
		const gods = ['女神1', '女神2', '女神3', '女神4', '女神5'];
		for(let i = 0; i < gods.length; i++){
			const g= gods[i];
			// 如果当前循环等待的Promise没有resolve,则下一次循环不运行
			const result = await biaobai(g);
			if(result){
				console.log(`${g}同意了，不再表白了`);
				break
			}else{
				console.log(`${g}没有同意`)
			}
		}
	}
	biaobaiAll()
</script>
await处理错误状态
<script>
	async function getPromise(){
		if(Math.random() < 0.5){
			return 1;
		}else{
			throw 2
		}
	}
	async function test(){
		try{
			const result = await getPromise();
			console.log('正常状态',result)
		}
		catch(err){
			console.log('错误状态',err)
		}
		// 等效于
		getPromise().then(data=>{
			const result = data;
			console.log('正常状态',result)
		},err=>{
			console.log('错误状态',err)
		})
	}
	test()
</script>
普通promise与await函数对比
<script>
	// 辅助函数,把传进来的对象拼接成url的字符串
	function toData(obj) {
		if (obj === null) {
			return obj;
		}
		let arr = [];
		for (let i in obj) {
			let str = i + "=" + obj[i];
			arr.push(str);
		}
		return arr.join("&");
	}
	// 封装Ajax
	function ajax(obj) {
		return new Promise((resolve, reject) => {
			//指定提交方式的默认值
			obj.type = obj.type || "get";
			//设置是否异步，默认为true(异步)
			obj.async = obj.async || true;
			//设置数据的默认值
			obj.data = obj.data || null;
			// 根据不同的浏览器创建XHR对象
			let xhr = null;
			if (window.XMLHttpRequest) {
				// 非IE浏览器
				xhr = new XMLHttpRequest();
			} else {
				// IE浏览器
				xhr = new ActiveXObject("Microsoft.XMLHTTP");
			}
			// 区分get和post,发送HTTP请求
			if (obj.type === "post") {
				xhr.open(obj.type, obj.url, obj.async);
				xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
				let data = toData(obj.data);
				xhr.send(data);
			} else {
				let url = obj.url + "?" + toData(obj.data);
				xhr.open(obj.type, url, obj.async);
				xhr.send();
			}
			// 接收返回过来的数据
			xhr.onreadystatechange = function() {
				if (xhr.readyState === 4) {
					if (xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) {
						resolve(JSON.parse(xhr.responseText))
					} else {
						reject(xhr.status)
					}
				}
			}
		})
	}


	//获取李华所在班级的老师的信息
	//1. 获取李华的班级id   Promise
	//2. 根据班级id获取李华所在班级的老师id   Promise
	//3. 根据老师的id查询老师信息   Promise
	//———————————————————————————————————————————普通Promise——————————————————————————————————————————//
	const pro = ajax({
		url: "./data/students.json"
	})
	pro.then(resp => {
		for (let i = 0; i < resp.length; i++) {
			if (resp[i].name === "李华") {
				return resp[i].classId; //班级id
			}
		}
	}).then(cid => {
		return ajax({
			url: "./data/classes.json?cid=" + cid
		}).then(cls => {
			for (let i = 0; i < cls.length; i++) {
				if (cls[i].id === cid) {
					return cls[i].teacherId;
				}
			}
		})
	}).then(tid => {
		return ajax({
			url: "./data/teachers.json"
		}).then(ts => {
			for (let i = 0; i < ts.length; i++) {
				if (ts[i].id === tid) {
					return ts[i];
				}
			}
		})
	}).then(teacher => {
		console.log(teacher);
	})
	//————————————————————————————————————————————————————————————————————————————————————————————————————//
	//————————————————————————————————————————————await———————————————————————————————————————————————————//
	async function getTeacher() {
		const stus = await ajax({
			url: "./data/students.json"
		})
		let cid;
		for (let i = 0; i < stus.length; i++) {
			if (stus[i].name === "李华") {
				cid = stus[i].classId;
			}
		}
		const cls = await ajax({
			url: "./data/classes.json"
		})
		let tid;
		for (let i = 0; i < cls.length; i++) {
			if (cls[i].id === cid) {
				tid = cls[i].teacherId;
			}
		}
		const ts = await ajax({
			url: "./data/teachers.json"
		})
		for (let i = 0; i < ts.length; i++) {
			const element = ts[i];
			if (element.id === tid) {
				console.log(element);
			}
		}
	}
	//————————————————————————————————————————————————————————————————————————————————————————————————————//
</script>
fetch Api 概述
XMLHttpRequest的问题
1.所有的功能全部集中在同一个对象上，容易书写出混乱不易维护的代码
2.采用传统的事件驱动模式，无法适配新的Promise API

fetch api的特点
1.并非取代ajax,而是对ajax传统api的改进
2.精细的功能分割：头部信息，请求信息，响应信息等均分布到不同的对象，更利于处理各种复杂的ajax场景
3.使用promise api，更利于异步代码的书写
4.fetch api并非es6的内容，属于HTML5 新增的web api(目前浏览器环境下支持该api,而node环境下并不支持该api)
5.需要掌握网络通信的知识

跨域与预检
跨域资源共享 CORS 详解		https://www.ruanyifeng.com/blog/2016/04/cors.html		
HTTP访问控制（CORS）		https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS

fetch的基本使用方法
<script>
	async function getProvinces(){
		const url = "http://101.132.72.36:5100/api/local";
		const resp = await fetch(url)
		const result = await resp.json();
		console.log(result)
	}
</script>
# Request对象
除了使用基本的fetch方法，还可以通过创建一个Request对象来完成请求（实际上，fetch的内部会帮你创建一个Request对象）
```js
new Request(url地址, 配置)
```
注意点：
尽量保证每次请求都是一个新的Request对象（这样做是为了防止在进行post请求时，当数据量过多时会形成流，流会记录上传的位置，当请求被复用时该进度也会被复用，导致第二次请求并不是在0%处开始）
如果Request对象确实要复用，则使用clone方法
<script>
	function getRequestInfo(){
		if(!req){
			const url = "http://101.132.72.36:5100/api/local";
			req = new Request(url, {});
			console.log(req);
		}
		return req.clone()	//克隆一个全新的request对象，配置一致，保证流传输时每次都重新进行传输
	}
	async function getProvinces(){
		const resp = await fetch(getRequestInfo())
		const result = await resp.json();
		console.log(result)
	}
</script>
# Response对象:
用来手动模拟服务器的返回对象
<script>
	async function getProvinces(){
		const resp = new Response(`[
			{"id: 1, "name": "北京"},
			{"id: 2, "name": "天津"}
		]`,{
			ok: true,
			status: 200
		})
		const result = await getJSON(resp);
		console.log(result);
	}
	async function getJSON(resp){
		const json = await resp.json();
		return json
	}
</script>
# Headers 对象

在Request和Response对象内部，会将传递的请求头对象，转换为Headers

Headers对象中的方法：

- has(key)：检查请求头中是否存在指定的key值
- get(key): 得到请求头中对应的key值
- set(key, value)：修改对应的键值对
- append(key, value)：添加对应的键值对
- keys(): 得到所有的请求头键的集合
- values(): 得到所有的请求头中的值的集合
- entries(): 得到所有请求头中的键值对的集合
<script>
	// 创建请求/相应头信息
	function getCommonHeaders() {
            return new Headers({
                a: 1,
                b: 2
            })
        }
	// 打印请求/相应头信息
        function printHeaders(headers) {
            const datas = headers.entries();
            for (const pair of datas) {
                console.log(`key: ${pair[0]}，value: ${pair[1]}`);
            }
        }
		// 发送请求
        function getRequestInfo() {
            if (!req) {
                const url = "http://101.132.72.36:5100/api/local";
                const headers = getCommonHeaders();
                headers.set("a", 3)
                req = new Request(url, {
                    headers
                });
                printHeaders(headers);
            }
            return req.clone(); //克隆一个全新的request对象，配置一致
        }
		// 使用await方法请求
        async function getProvinces() {
            const resp = await fetch(getRequestInfo())
            printHeaders(resp.headers);
            const result = await getJSON(resp);
            console.log(result)
        }
		// 相应体转化为json对象方法
        async function getJSON(resp) {
            const json = await resp.json();
            return json;
        }

        document.querySelector("button").onclick = function() {
            getProvinces();
        }
</script>
# 文件上传

流程：

1. 客户端将文件数据发送给服务器
2. 服务器保存上传的文件数据到服务器端
3. 服务器响应给客户端一个文件访问地址

> 测试地址：http://101.132.72.36:5100/api/upload
> 键的名称（表单域名称）：imagefile

请求方法：POST
请求的表单格式：multipart/form-data
请求体中必须包含一个键值对，键的名称是服务器要求的名称，值是文件数据

> HTML5中，JS仍然无法随意的获取文件数据，但是可以获取到input元素中，被用户选中的文件数据
> 可以利用HTML5提供的FormData构造函数来创建请求体

传统form表单格式上传文件：
<form action="" enctype="multipart/form-data"></form>
ajax文件上传：
	<img src="" alt="" id="imgAvatar">
	单文件上传：<input type="file" id="avatar">
	<!-- 多文件上传：<input type="file" multiple id="avatar"> -->
    <button>上传</button>
    <script>
        async function upload() {
            const inp = document.getElementById("avatar");
            if (inp.files.length === 0) {
                alert("请选择要上传的文件");
                return;
            }
            const formData = new FormData(); //构建请求体
            formData.append("imagefile", inp.files[0]);	// "imagefile"为和后端约定好的键名，inp.files[0]为要上传的文件数据
            const url = "http://101.132.72.36:5100/api/upload"
            const resp = await fetch(url, {
                method: "POST",
                body: formData //设置body为自己创建的formData后会，自动修改请求头
            });
            const result = await resp.json();
            return result;
        }

        document.querySelector("button").onclick = async function() {
            const result = await upload();
            const img = document.getElementById("imgAvatar")
            img.src = result.path;
        }
	</script>
# 迭代器

## 背景知识

1. 什么是迭代？

从一个数据集合中按照一定的顺序，不断取出数据的过程

2. 迭代和遍历的区别？

迭代强调的是依次取数据，并不保证取多少，也不保证把所有的数据取完

遍历强调的是要把整个数据依次全部取出

3. 迭代器

对迭代过程的封装，在不同的语言中有不同的表现形式，通常为对象

4. 迭代模式

一种设计模式，用于统一迭代过程，并规范了迭代器规格：

- 迭代器应该具有得到下一个数据的能力
- 迭代器应该具有判断是否还有后续数据的能力

## JS中的迭代器

JS规定，如果一个对象具有next方法，并且该方法返回一个对象，该对象的格式如下：

```js
{value: 值, done: 是否迭代完成}
```

则认为该对象是一个迭代器

含义：

- next方法：用于得到下一个数据
- 返回的对象
	- value：下一个数据的值
	- done：boolean，是否迭代完成

	使用迭代器来实现遍历数据
    <script>
        const arr = [1, 2, 3, 4, 5];
        //迭代数组arr
        const iterator = {
            i: 0, //当前的数组下标
            next() {
                var result = {
                    value: arr[this.i],
                    done: this.i >= arr.length
                }
                this.i++;
                return result;
            }
        }

        //让迭代器不断的取出下一个数据，直到没有数据为止
        let data = iterator.next();
        while (!data.done) { //只要没有迭代完成，则取出数据
            console.log(data.value)
            //进行下一次迭代
            data = iterator.next();
        }

        console.log("迭代完成")
	</script>	
	迭代器创建函数
	<script>
        const arr1 = [1, 2, 3, 4, 5];
        const arr2 = [6, 7, 8, 9];

        // 迭代器创建函数  iterator creator
        function createIterator(arr) {
            let i = 0;//当前的数组下标
            return { 
                next() {
                    var result = {
                        value: arr[i],
                        done: i >= arr.length
                    }
                    i++;
                    return result;
                }
            }
        }
        const iter1 = createIterator(arr1);
        const iter2 = createIterator(arr2);
	</script>
	斐波那契数列迭代器
	<script>
        // 依次得到斐波拉契数列前面n位的值
        // 1 1 2 3 5 8 13 .....

        //创建一个斐波拉契数列的迭代器
        function createFeiboIterator() {
            let prev1 = 1,
                prev2 = 1, //当前位置的前1位和前2位
                n = 1; //当前是第几位

            return {
                next() {
                    let value;
                    if (n <= 2) {
                        value = 1;
                    } else {
                        value = prev1 + prev2;
                    }
                    const result = {
                        value,
                        done: false
                    };
                    prev2 = prev1;
                    prev1 = result.value;
                    n++;
                    return result;
                }
            }
        }

        const iterator = createFeiboIterator();
        
	</script>
	# 可迭代协议 与 for-of 循环

	## 可迭代协议
	
	**概念回顾**
	
	- 迭代器(iterator)：一个具有next方法的对象，next方法返回下一个数据并且能指示是否迭代完成
	- 迭代器创建函数（iterator creator）：一个返回迭代器的函数
	
	**可迭代协议**
	
	ES6规定，如果一个对象具有知名符号属性```Symbol.iterator```，并且属性值是一个迭代器创建函数，则该对象是可迭代的（iterable）
	
	> 思考：如何知晓一个对象是否是可迭代的？
	> 思考：如何遍历一个可迭代对象？
	
	## for-of 循环
	
	for-of 循环用于遍历可迭代对象，格式如下
	
	```js
	//迭代完成后循环结束
	for(const item in iterable){
		//iterable：可迭代对象
		//item：每次迭代得到的数据
	}
	```
	
	## 展开运算符与可迭代对象
	
	展开运算符可以作用于可迭代对象，这样，就可以轻松的将可迭代对象转换为数组（注意，这里说的是对象类型数据展开为数组时的情况也就是const obj={}, [...obj]，对象类型的对象扩展为对象时不会报错比如const obj = {}, {...obj}）。
	手动封装可迭代对象
	<script>
        //可迭代对象
        var obj = {
            a: 1,
            b: 2,
            [Symbol.iterator]() {
                const keys = Object.keys(this);
                let i = 0;
                return {
                    next: () => {
                        const propName = keys[i];
                        const propValue = this[propName];
                        const result = {
                            value: {
                                propName,
                                propValue
                            },
                            done: i >= keys.length
                        }
                        i++;
                        return result;
                    }
                }
            }
        }

        for (const item of obj) {
            console.log(item); // {propName:"a", propValue:1}
        }
	</script>
	for of语法与可迭代对象
	<script>
        const arr = [5, 7, 2, 3, 6];
        for (const item of arr) {
            console.log(item)
        }
		// 等效于
		// const iterator = arr[Symbol.iterator]();
        // let result = iterator.next();
        // while (!result.done) {
        //     const item = result.value; //取出数据
        //     console.log(item);
        //     //下一次迭代
        //     result = iterator.next();
        // }
	</script>
	对象类型的数据扩展为数组，需要手动添加迭代器
	<script>
        var obj = {
            a: 1,
            b: 2,
            [Symbol.iterator]() {
                const keys = Object.keys(this);
                let i = 0;
                return {
                    next: () => {
                        const propName = keys[i];
                        const propValue = this[propName];
                        const result = {
                            value: {
                                propName,
                                propValue
                            },
                            done: i >= keys.length
                        }
                        i++;
                        return result;
                    }
                }
            }
        }

        const arr = [...obj];
        console.log(arr);

        function test(a, b) {
            console.log(a, b)
        }

        test(...obj);
	</script>
	# 生成器 (Generator)

	1. 什么是生成器？
	
	生成器是一个通过构造函数Generator创建的对象（用户无法使用构造函数Generator，只能由系统内部调用），生成器既是一个迭代器（包含next方法），同时又是一个可迭代对象（可以使用for of迭代这个对象）
	
	2. 如何创建生成器？
	
	生成器的创建，必须使用生成器函数（Generator Function）
	
	3. 如何书写一个生成器函数呢？
	
	```js
	//这是一个生成器函数，该函数一定返回一个生成器
	function* method(){
	
	}
	// 在对象内的生成器
	{
		*method(){}
	}
	```
	
	4. 生成器函数内部是如何执行的？

	生成器函数内部是为了给生成器的每次迭代提供的数据
	
	每次调用生成器的next方法，将导致生成器函数运行到下一个yield关键字位置
	
	yield是一个关键字，该关键字只能在生成器函数内部使用，表达“产生”一个迭代数据。
	
	5. 有哪些需要注意的细节？
	
	1). 生成器函数可以有返回值（return），返回值出现在第一次done为true时的value属性中
	2). 调用生成器的next方法时，可以传递参数，传递的参数会交给yield表达式的返回值
	3). 第一次调用next方法时，传参没有任何意义
	4). 在生成器函数内部，可以调用其他生成器函数，但是要注意加上*号
	
	
	6. 生成器的其他API
	
	- return方法：调用该方法，可以提前结束生成器函数，从而提前让整个迭代过程结束
	- throw方法：调用该方法，可以在生成器中产生一个错误

	生成器函数使用
	<script>
        function* test() {
            console.log("第1次运行")
            yield 1;
            console.log("第2次运行")
            yield 2;
            console.log("第3次运行")
        }

        const generator = test();	// test只是一个生成器函数，调用test()只是简单的得到了一个生成器而已，并不会运行test函数体内的代码
									// 生成器函数内部是为了给生成器的每次迭代提供数据（数据写在yield后面）
	</script>

	使用生成器来创建迭代器
	<script>
        const arr1 = [1, 2, 3, 4, 5];
        const arr2 = [6, 7, 8, 9];

        // 迭代器创建函数  iterator creator
        function* createIterator(arr) {
            for (const item of arr) {
                yield item;
            }
        }

        const iter1 = createIterator(arr1);
        const iter2 = createIterator(arr2);

	</script>
	创建斐波那契迭代器
	<script>
        //创建一个斐波拉契数列的迭代器
        function* createFeiboIterator() {
            let prev1 = 1,
                prev2 = 1, //当前位置的前1位和前2位
                n = 1; //当前是第几位
            while (true) {
                if (n <= 2) {
                    yield 1;
                } else {
                    const newValue = prev1 + prev2
                    yield newValue;
                    prev2 = prev1;
                    prev1 = newValue;
                }
                n++;
            }
        }

        const iterator = createFeiboIterator();
	</script>
	传参的生成器函数
	<script>
									// 第一次调用next方法时，传参没有任何意义
		function* test(){			// generator.next()		————>	输出{value:1, done:false}   
			let info = yield 1;		// generator.next(5)	————>	此时info = 5
			console.log(info)		// 打印5
			info = yield 2 + info;	// generator.next()		————>	输出{value:7, done:false}	此时info赋值为undefined
			console.log(info)		// 打印undefined
		}
		const generator = test();
	</script>
	在生成器内部调用其他的生成器
	<script>
        function* t1(){
            yield "a"
            yield "b"
        }

        function* test() {
            yield* t1();	// 让t1迭代器参与迭代
			// 等效于
			// yield "a"
            // yield "b"
            yield 1;
            yield 2;
            yield 3;
        }

        const generator = test();
	</script>
	使用迭代器模拟await
	<script>
        function* task() {
            const d = yield 1;
            console.log(d)
            // //d : 1
            const resp = yield fetch("http://101.132.72.36:5100/api/local")
            const result = yield resp.json();
            console.log(result);
        }

        run(task)

        function run(generatorFunc) {
            const generator = generatorFunc();
            let result = generator.next(); //启动任务（开始迭代）, 得到迭代数据
            handleResult();
            //对result进行处理
            function handleResult() {
                if (result.done) {
                    return; //迭代完成，不处理
                }
                //迭代没有完成，分为两种情况
                //1. 迭代的数据是一个Promise
                //2. 迭代的数据是其他数据
                if (typeof result.value.then === "function") {
                    //1. 迭代的数据是一个Promise
                    //等待Promise完成后，再进行下一次迭代
                    result.value.then(data => {
                        result = generator.next(data)
                        handleResult();
                    },err => {
                        result = generator.throw(err);
                        handleResult();
                    })
                } else {
                    //2. 迭代的数据是其他数据，直接进行下一次迭代，把同步代码返回的数据（yield后面的数据的value值）传入，进行下一次迭代
                    result = generator.next(result.value)
                    handleResult();
                }
            }
        }
	</script>
	# set 集合

	> 一直以来，JS只能使用数组和对象来保存多个数据，缺乏像其他语言那样拥有丰富的集合类型。因此，ES6新增了两种集合类型（set 和 map），用于在不同的场景中发挥作用。
	
	**set用于存放不重复的数据**
	
	1. 如何创建set集合
	
	```js
	new Set(); //创建一个没有任何内容的set集合
	
	new Set(iterable); //创建一个具有初始内容的set集合，内容来自于可迭代对象每一次迭代的结果
	
	```
	
	2. 如何对set集合进行后续操作
	
	- add(数据): 添加一个数据到set集合末尾，如果数据已存在，则不进行任何操作
	  - set使用Object.is的方式判断两个数据是否相同，但是，针对+0和-0，set认为是相等
	- has(数据): 判断set中是否存在对应的数据
	- delete(数据)：删除匹配的数据，返回是否删除成功
	- clear()：清空整个set集合
	- size: 获取set集合中的元素数量，只读属性，无法重新赋值
	
	3. 如何与数组进行相互转换
	
	```js
	const s = new Set([x,x,x,x,x]);
	// set本身也是一个可迭代对象，每次迭代的结果就是每一项的值
	const arr = [...s];
	```
	
	4. 如何遍历
	
	1). 使用for-of循环
	2). 使用set中的实例方法forEach
	
	注意：set集合中不存在下标（下标是数组特有的东西，并不是所有集合都有下标），因此forEach中的回调的第二个参数和第一个参数是一致的，均表示set中的每一项

	使用set做去重
	<script>
		// 数组去重
		const arr = [45, 7, 2, 2, 34, 46, 6, 57, 8, 55, 6, 46];
        const result = [...new Set(arr)]
		console.log(result)
		// 字符串去重
		const str = "asf23sdfgsdgfsafasdfasfasfasfsafsagfdsfg";
        const s = [...new Set(str)].join("");
        console.log(s);
	</script>
	set应用：求交集、并集、差集
	<script>
        // 两个数组的并集、交集、差集 （不能出现重复项），得到的结果是一个新数组
        const arr1 = [33, 22, 55, 33, 11, 33, 5];
        const arr2 = [22, 55, 77, 88, 88, 99, 99];

        //并集
        // const result = [...new Set(arr1.concat(arr2))];
        console.log("并集", [...new Set([...arr1, ...arr2])]);

        const cross = [...new Set(arr1)].filter(item => arr2.indexOf(item) >= 0);
        //交集
        console.log("交集", cross)

        //差集
        // console.log("差集", [...new Set([...arr1, ...arr2])].filter(item => arr1.indexOf(item) >= 0 && arr2.indexOf(item) < 0 || arr2.indexOf(item) >= 0 && arr1.indexOf(item) < 0))
        console.log("差集", [...new Set([...arr1, ...arr2])].filter(item => cross.indexOf(item) < 0))
	</script>
	手写set
	<script>
		class MySet {
    constructor(iterator = []) {
        //验证是否是可迭代的对象
        if (typeof iterator[Symbol.iterator] !== "function") {
            throw new TypeError(`你提供的${iterator}不是一个可迭代的对象`)
        }
        this._datas = [];
        for (const item of iterator) {
            this.add(item);
        }
    }

    get size() {
        return this._datas.length;
    }

    add(data) {
        if (!this.has(data)) {
            this._datas.push(data);
        }
    }

    has(data) {
        for (const item of this._datas) {
            if (this.isEqual(data, item)) {
                return true;
            }
        }
        return false;
    }

    delete(data) {
        for (let i = 0; i < this._datas.length; i++) {
            const element = this._datas[i];
            if (this.isEqual(element, data)) {
                //删除
                this._datas.splice(i, 1);
                return true;
            }
        }
        return false;
    }

    clear() {
        this._datas.length = 0;
    }

    *[Symbol.iterator]() {
        for (const item of this._datas) {
            yield item;
        }
    }

    forEach(callback) {
        for (const item of this._datas) {
            callback(item, item, this);
        }
    }

    /**
     * 判断两个数据是否相等
     * @param {*} data1 
     * @param {*} data2 
     */
    isEqual(data1, data2) {
        if (data1 === 0 && data2 === 0) {
            return true;
        }
        return Object.is(data1, data2);
    }
}
	</script>

# map集合

键值对（key value pair）数据集合的特点：键不可重复

map集合专门用于存储多个键值对数据。

在map出现之前，我们使用的是对象的方式来存储键值对，键是属性名，值是属性值。

使用对象存储有以下问题：

1. 键名只能是字符串
2. 获取数据的数量不方便
3. 键名容易跟原型上的名称冲突


1. 如何创建map

```js
new Map(); //创建一个空的map
new Map(iterable); //创建一个具有初始内容的map，初始内容来自于可迭代对象每一次迭代的结果，但是，它要求每一次迭代的结果必须是一个长度为2的数组，数组第一项表示键，数组的第二项表示值
```

2. 如何进行后续操作

- size：只读属性，获取当前map中键的数量
- set(键, 值)：设置一个键值对，键和值可以是任何类型
  - 如果键不存在，则添加一项
  - 如果键已存在，则修改它的值
  - 比较键的方式和set相同
- get(键): 根据一个键得到对应的值
- has(键)：判断某个键是否存在
- delete(键)：删除指定的键
- clear(): 清空map


3. 和数组互相转换

和set一样

4. 遍历

- for-of，每次迭代得到的是一个长度为2的数组
- forEach，通过回调函数遍历
  - 参数1：每一项的值
  - 参数2：每一项的键
  - 参数3：map本身

手写map
<script>
	class MyMap {
    constructor(iterable = []) {
        //验证是否是可迭代的对象
        if (typeof iterable[Symbol.iterator] !== "function") {
            throw new TypeError(`你提供的${iterable}不是一个可迭代的对象`)
        }
        this._datas = [];
        for (const item of iterable) {
            // item 也得是一个可迭代对象
            if (typeof item[Symbol.iterator] !== "function") {
                throw new TypeError(`你提供的${item}不是一个可迭代的对象`);
            }
            const iterator = item[Symbol.iterator]();
            const key = iterator.next().value;		// 获取每一个item的key和value，不要使用 key = item[0], value = item[1],因为这样写只是能获取到数组的key和value，而map只要求每个item是可迭代对象即可，所以要使用这种方式来获取每一项的key和value
            const value = iterator.next().value;
            this.set(key, value);
        }

    }

    set(key, value) {
        const obj = this._getObj(key);
        if (obj) {
            //修改
            obj.value = value;
        }
        else {
            this._datas.push({
                key,
                value
            })
        }
    }

    get(key) {
        const item = this._getObj(key);
        if (item) {
            return item.value;
        }
        return undefined;
    }

    get size() {
        return this._datas.length;
    }

    delete(key) {
        for (let i = 0; i < this._datas.length; i++) {
            const element = this._datas[i];
            if (this.isEqual(element.key, key)) {
                this._datas.splice(i, 1);
                return true;
            }
        }
        return false;
    }

    clear() {
        this._datas.length = 0;
    }

    /**
     * 根据key值从内部数组中，找到对应的数组项
     * @param {*} key 
     */
    _getObj(key) {
        for (const item of this._datas) {
            if (this.isEqual(item.key, key)) {
                return item;
            }
        }
    }

    has(key) {
        return this._getObj(key) !== undefined;
    }

    /**
     * 判断两个数据是否相等
     * @param {*} data1 
     * @param {*} data2 
     */
    isEqual(data1, data2) {
        if (data1 === 0 && data2 === 0) {
            return true;
        }
        return Object.is(data1, data2);
    }

    *[Symbol.iterator]() {
        for (const item of this._datas) {
            yield [item.key, item.value];
        }
    }

    forEach(callback) {
        for (const item of this._datas) {
            callback(item.value, item.key, this);
        }
    }
}
</script>
# WeakSet 和 WeakMap

## WeakSet

使用该集合，可以实现和set一样的功能，不同的是：

## WeakSet主要用作检测对象的引用是否清空
1. **它内部存储的对象地址不会影响垃圾回收**
2. 只能添加对象
3. 不能遍历（不是可迭代的对象）、没有size属性、没有forEach方法

## WeakMap

类似于map的集合，不同的是：

1. **它的键存储的地址不会影响垃圾回收**
2. 它的键只能是对象
3. 不能遍历（不是可迭代的对象）、没有size属性、没有forEach方法

weakmap应用实例

    <ul>
        <!-- { id:"1", name:"姓名1" } -->
        <li>1</li>
        <!-- { id:"2", name:"姓名2" } -->
        <li>2</li>
        <!-- { id:"3", name:"姓名3" } -->
        <li>3</li>
    </ul>
    <script>
		// 每个li与一条数据相关联，当其中一个li的引用被彻底清除后（包括在dom上的引用和在js上的引用两个地方的引用）,在weakmap上对应的数据也将被清除
        const wmap = new WeakMap();
        let lis = document.querySelectorAll("li");
        for (const li of lis) {
            wmap.set(li, {
                id: li.innerHTML,
                name: `姓名${li.innerHTML}`
            });
        }
        lis[0].remove();
        lis = null;
        
        console.log(wmap);
    </script>
# 属性描述符

Property Descriptor 属性描述符  是一个普通对象，用于描述一个属性的相关信息

通过```Object.getOwnPropertyDescriptor(对象, 属性名)```可以得到一个对象的某个属性的属性描述符

- value：属性值
- configurable：该属性的描述符是否可以修改
- enumerable：该属性是否可以被枚举，这个配置会影响到object.keys和object.values
- writable：该属性的值是否可以被重新赋值

> ```Object.getOwnPropertyDescriptors(对象)```可以得到某个对象的所有属性描述符

如果需要为某个对象添加属性时 或 修改属性时， 配置其属性描述符，可以使用下面的代码:

```js
Object.defineProperty(对象, 属性名, 描述符);
Object.defineProperties(对象, 多个属性的描述符)
```

## 存取器属性

属性描述符中，如果配置了 get 和 set 中的任何一个，则该属性，不再是一个普通属性，而变成了存取器属性。

get 和 set配置均为函数，如果一个属性是存取器属性，则读取该属性时，会运行get方法，将get方法得到的返回值作为属性值；如果给该属性赋值，则会运行set方法。

存取器属性最大的意义，在于可以控制属性的读取和赋值。

使用存取器属性来实现元素与数据的同步
<body>
    <p>
        <span>姓名：</span>
        <span id="name"></span>
    </p>
    <p>
        <span>年龄：</span>
        <span id="age"></span>
    </p>
    <script>
        const spanName = document.getElementById("name")
        const spanAge = document.getElementById("age")

        const user = {}

        Object.defineProperties(user, {
            name: {
                get() {
                    return spanName.innerText;
                },
                set(val) {
                    spanName.innerText = val;
                }
            },
            age: {
                get() {
                    return +spanAge.innerText;
                },
                set(val) {
                    if (typeof val !== "number") {
                        throw new TypeError("年龄必须是一个数字")
                    }
                    if (val < 0) {
                        val = 0;
                    } else if (val > 200) {
                        val = 200;
                    }
                    spanAge.innerText = val;
                }
            }
        })
    </script>
</body>
使用Object.defineProperty时
{
	value: 3,
	configurable: false,
	enumerable: false,
	writable: false
} 与 get/set不能同时存在，否则会报错，因为设置value就是给属性分配了一块内存空间，而set/get是使用了寄存器方法也就抛弃了分配内存空间的方式。

浏览器中dom元素的很多属性都是存取器属性，模拟dom元素内的存取器属性：
<script>
	function HTMLSpanElement(){
		// dom元素的innerHTML使用存储器属性，而没有使用普通的对象属性，是因为如果使用普通对象属性的话在innerHTML更改后，
		// 浏览器无法知道什么时候更改了，所以也就无法实时获取更改后的信息，并渲染页面
		Object.defineProperty(this, "innerHTML", {
			get(){
				// 获取页面窗口中对应的元素内容
			},
			set(){
				// 重新渲染页面窗口
			}
		})
	}
</script>

# Reflect

1. Reflect是什么？

Reflect是一个内置的JS对象，它提供了一系列方法，可以让开发者通过调用这些方法，访问一些JS底层功能

由于它类似于其他语言的**反射**，因此取名为Reflect

2. 它可以做什么？

使用Reflect可以实现诸如 属性的赋值与取值、调用普通函数、调用构造函数、判断属性是否存在与对象中  等等功能

3. 这些功能不是已经存在了吗？为什么还需要用Reflect实现一次？

有一个重要的理念，在ES5就被提出：减少魔法、让代码更加纯粹

这种理念很大程度上是受到函数式编程的影响

ES6进一步贯彻了这种理念，它认为，对属性内存的控制、原型链的修改、函数的调用等等，这些都属于底层实现，属于一种魔法，因此，需要将它们提取出来，形成一个正常的API，并高度聚合到某个对象中，于是，就造就了Reflect对象

因此，你可以看到Reflect对象中有很多的API都可以使用过去的某种语法或其他API实现。

4. 它里面到底提供了哪些API呢？

- Reflect.set(target, propertyKey, value): 设置对象target的属性propertyKey的值为value，等同于给对象的属性赋值
- Reflect.get(target, propertyKey): 读取对象target的属性propertyKey，等同于读取对象的属性值
- Reflect.apply(target, thisArgument, argumentsList)：调用一个指定的函数，并绑定this和参数列表。等同于函数调用
- Reflect.deleteProperty(target, propertyKey)：删除一个对象的属性
- Reflect.defineProperty(target, propertyKey, attributes)：类似于Object.defineProperty，不同的是如果配置出现问题，返回false而不是报错
- Reflect.construct(target, argumentsList)：用构造函数的方式创建一个对象， const obj = Reflect.construct(Test, [1, 2, 3, 4])相当于 const obj = new Test(1, 2, 3, 4)
- Reflect.has(target, propertyKey): 判断一个对象是否拥有一个属性
- 其他API：https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect
<script>
	function method(a, b){
	    console.log("method", a, b);
	}

	// method(3, 4);

	Reflect.apply(method, null, [3, 4])

	const obj = {
	    a: 1,
	    b: 2
	}

	// delete obj.a;

	Reflect.deleteProperty(obj, "a");

	console.log(obj);

	function Test(a, b) {
	    this.a = a;
	    this.b = b;
	}

	// const t = new Test(1, 3);
	const t = Reflect.construct(Test, [1, 3]);
	console.log(t)

	const obj = {
		a: 1,
		b: 2
	}

	// console.log("a" in obj);
	console.log(Reflect.has(obj, "a"));
</script>
# Proxy 代理

代理：提供了修改底层实现的方式

```js

//代理一个目标对象
//target：目标对象
//handler：是一个普通对象，其中可以重写底层实现
//返回一个代理对象
new Proxy(target, handler)
```
<script>
	const obj = {
		a: 1,
		b: 2
	}

	const proxy = new Proxy(obj, {
		set(target, propertyKey, value) {
			// console.log(target, propertyKey, value);
			// target[propertyKey] = value;
			Reflect.set(target, propertyKey, value);
		},
		get(target, propertyKey) {
			if (Reflect.has(target, propertyKey)) {
				return Reflect.get(target, propertyKey);
			} else {
				return -1;
			}
		},
		has(target, propertyKey) {
			return false;
		}
	});
	// console.log(proxy);
	// proxy.a = 10;
	// console.log(proxy.a);

	console.log(proxy.d);
	console.log("a" in proxy);
</script>
# 观察者模式

有一个对象，是观察者，它用于观察另外一个对象的属性值变化，当属性值变化后会收到一个通知，可能会做一些事。

使用Objet.defineProperty实现：
<div id="container"></div>
<script>
	// 缺点：1.无法对新增的属性进行观察，只能对已有的属性进行观察，比如新增了obj.c = 3无法观察到
	//		2.为了实现观察需要再创建一个对象（观察者对象），导致内存被浪费，浪费性能
	//创建一个观察者
	function observer(target) {
		const div = document.getElementById("container");
		const ob = {};
		const props = Object.keys(target);
		for (const prop of props) {
			Object.defineProperty(ob, prop, {
				get() {
					return target[prop];
				},
				set(val) {
					target[prop] = val;
					render();
				},
				enumerable: true
			})
		}
		render();

		function render() {
			let html = "";
			for (const prop of Object.keys(ob)) {
				html += `
					<p><span>${prop}：</span><span>${ob[prop]}</span></p>
				`;
			}
			div.innerHTML = html;
		}

		return ob;
	}
	const target = {
		a: 1,
		b: 2
	}
	const obj = observer(target)
</script>
使用Proxy
<script>
	//创建一个观察者
	function observer(target) {
		const div = document.getElementById("container");
		const proxy = new Proxy(target, {
			set(target, prop, value) {
				Reflect.set(target, prop, value);
				render();
			},
			get(target, prop){
				return Reflect.get(target, prop);
			}
		})
		render();

		function render() {
			let html = "";
			for (const prop of Object.keys(target)) {
				html += `
					<p><span>${prop}：</span><span>${target[prop]}</span></p>
				`;
			}
			div.innerHTML = html;
		}

		return proxy;
	}
	const target = {
		a: 1,
		b: 2
	}
	const obj = observer(target)
</script>
使用代理的construct来实现偷懒的构造函数
<script>
	class User {

	}

	function ConstructorProxy(Class, ...propNames) {
		return new Proxy(Class, {
			construct(target, argumentsList) {
				const obj = Reflect.construct(target, argumentsList)
				propNames.forEach((name, i) => {
					obj[name] = argumentsList[i];
				})
				return obj;
			}
		})
	}

	const UserProxy = ConstructorProxy(User, "firstName", "lastName", "age")

	const obj = new UserProxy("袁", "进", 18);
	console.log(obj)

	class Monster {

	}

	const MonsterProxy = ConstructorProxy(Monster, "attack", "defence", "hp", "rate", "name")

	const m = new MonsterProxy(10, 20, 100, 30, "怪物")
	console.log(m);
</script>
使用代理的apply功能来实现函数传入参数的类型校验
<script>
	function sum(a, b) {
		return a + b;
	}

	function validatorFunction(func, ...types) {
		const proxy = new Proxy(func, {
			apply(target, thisArgument, argumentsList) {
				types.forEach((t, i) => {
					const arg = argumentsList[i]
					if (typeof arg !== t) {
						throw new TypeError(`第${i+1}个参数${argumentsList[i]}不满足类型${t}`);
					}
				})
				return Reflect.apply(target, thisArgument, argumentsList);
			}
		})
		return proxy;
	}

	const sumProxy = validatorFunction(sum, "number", "number")
	console.log(sumProxy(1, 2))
</script>
使用非代理对象(使用高阶函数来实现上述功能)
<script>
	function sum(a, b) {
		return a + b;
	}

	function validatorFunction(func, ...types) {
		return function(...argumentsList) {
			types.forEach((t, i) => {
				const arg = argumentsList[i]
				if (typeof arg !== t) {
					throw new TypeError(`第${i+1}个参数${argumentsList[i]}不满足类型${t}`);
				}
			})
			return func(...argumentsList)
		}
	}

	const sumProxy = validatorFunction(sum, "number", "number")
	console.log(sumProxy(1, 2))
</script>
# 新增的数组API

## 静态方法

- Array.of(...args): 使用指定的数组项创建一个新数组
- Array.from(arg): 通过给定的类数组 或 可迭代对象 创建一个新的数组。

## 实例方法

- find(callback): 用于查找满足条件的第一个元素
- findIndex(callback)：用于查找满足条件的第一个元素的下标
- fill(data)：用指定的数据填充满数组所有的内容
- copyWithin(target, start?, end?): 在数组内部完成复制
- includes(data)：判断数组中是否包含某个值，使用Object.is匹配

# [扩展]类型化数组

## 数字存储的前置知识

1. 计算机必须使用固定的位数来存储数字，无论存储的数字是大是小，在内存中占用的空间是固定的。

2. n位的无符号整数能表示的数字是2^n个，取值范围是：0 ~ 2^n - 1

3. n位的有符号整数能表示的数字是2^n个，取值范围是：-2^(n-1) ~ 2^(n-1) - 1

4. 浮点数表示法可以用于表示整数和小数，目前分为两种标准：
   1. 32位浮点数：又称为单精度浮点数，它用1位表示符号，8位表示阶码，23位表示尾数
   2. 64位浮点数：又称为双精度浮点数，它用1位表示符号，11位表示阶码，52位表示尾数

5. JS中的所有数字，均使用双精度浮点数保存

## 类型化数组

类型化数组：用于优化多个数字的存储

具体分为：

- Int8Array： 8位有符号整数（-128 ~ 127）
- Uint8Array： 8位无符号整数（0 ~ 255）
- Int16Array: ...
- Uint16Array: ...
- Int32Array: ...
- Uint32Array: ...
- Float32Array:
- Float64Array

1. 如何创建数组

```js

new 数组构造函数(长度)

数组构造函数.of(元素...)

数组构造函数.from(可迭代对象)

new 数组构造函数(其他类型化数组)

```


2. 得到长度

```js
数组.length   //得到元素数量
数组.byteLength //得到占用的字节数
```

3. 其他的用法跟普通数组一致，但是：

- 不能增加和删除数据，类型化数组的长度固定
- 一些返回数组的方法，返回的数组是同类型化的新数组

# ArrayBuffer

ArrayBuffer：一个对象，用于存储一块固定内存大小的数据。

```js

new ArrayBuffer(字节数)

```

可以通过属性```byteLength```得到字节数，可以通过方法```slice```得到新的ArrayBuffer

## 读写ArrayBuffer

1. 使用DataView

通常会在需要混用多种存储格式时使用DataView

2. 使用类型化数组

实际上，每一个类型化数组都对应一个ArrayBuffer，如果没有手动指定ArrayBuffer，类型化数组创建时，会新建一个ArrayBuffer
将一个彩色图片转为黑白图
<div style="height: 117px; display: flex;">
	<img src="./img/liao.jpg" alt="">
	<button onclick="test()">转换</button>
	<canvas id="cvs" height="117"></canvas>
</div>
<script>
	function test() {
		const img = document.querySelector("img");
		const cvs = document.getElementById("cvs").getContext("2d");
		cvs.drawImage(img, 0, 0);
		const imgData = cvs.getImageData(0, 0, img.width, img.height);
		const data = imgData.data;
		for (let i = 0; i < data.length; i += 4) {
			const red = data[i],
				green = data[i + 1],
				blue = data[i + 2];
			const avg = (red + green + blue) / 3;
			data[i] = data[i + 1] = data[i + 2] = red;
		}
		cvs.putImageData(imgData, 0, 0);
	}
</script>
</body>
</html>